Skip to content

fix: compose extensions when any parent is active#39

Open
igrigorik wants to merge 2 commits into
mainfrom
fix/multi-parent-composition
Open

fix: compose extensions when any parent is active#39
igrigorik wants to merge 2 commits into
mainfrom
fix/multi-parent-composition

Conversation

@igrigorik

Copy link
Copy Markdown
Contributor

The UCP intersection algorithm states:

Remove any capability where extends is set but none of its parent capabilities are in the intersection...
For multi-parent extensions (extends: ["a", "b"]): at least one parent must be present.

The composer required every parent in extends[] to be present in the active response graph. For example, Discount can extend either Cart or Checkout:

{
  "dev.ucp.shopping.discount": [{
    "extends": [
      "dev.ucp.shopping.cart",
      "dev.ucp.shopping.checkout"
    ]
  }]
}

A response containing Checkout + Discount capability, but not Cart, previously failed with:

extension 'dev.ucp.shopping.discount' references unknown parent 'dev.ucp.shopping.cart'

Updated logic allows above / aligns with normative requirement in the UCP spec.


Checklist

  • Infrastructure: CI/CD, Linters, or build scripts. (Requires DevOps Maintainer approval)
  • I have followed the Contributing Guide
  • I have updated the documentation (if applicable).
  • My changes pass all local linting and formatting checks.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.

The UCP intersection algorithm states:

“Remove any capability where `extends` is set but none of its parent
capabilities are in the intersection.”

“For multi-parent extensions (`extends: ["a", "b"]`): at least one parent
must be present.”

— docs/specification/overview.md, “Intersection Algorithm”

The composer instead required every declared parent to be active. Discount
extending `[Cart, Checkout]` therefore failed to compose into a Checkout
response when Cart was absent.

Remove the all-parents-present check and retain root-reachability validation.
Extensions with no path to the active root remain rejected. Add Cart and
Checkout regressions for root-specific `$defs` selection, parent ordering,
orphan rejection, and cycle termination.
@igrigorik igrigorik self-assigned this Jul 9, 2026
@igrigorik
igrigorik requested a review from wry-ry July 9, 2026 23:18
@damaz91 damaz91 added status:needs-triage Signal that the PR is ready for human triage status:under-review and removed status:needs-triage Signal that the PR is ready for human triage labels Jul 9, 2026
@damaz91
damaz91 self-requested a review July 13, 2026 07:52
Comment thread src/compose.rs
if let Some(parents) = &cap.extends {
for parent in parents {
if !cap_map.contains_key(parent.as_str()) {
return Err(ComposeError::UnknownParent {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider cleaning up this error variant from src/error.rs if, after this PR, it's not referenced elsewhere

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants