docs: clarify UCP core release versioning contract#614
Conversation
UCP intentionally gave services, capabilities, and extensions their own version fields so they could evolve independently from ucp.version. The specification described that flexibility as part of the protocol model. In practice, UCP's publishing and certification process settled on an atomic release contract: each dated core release ships its services and transport bindings, capabilities, extensions, and shared schemas as one tested snapshot. This commit codifies that current contract rather than continuing to imply independent release schedules that UCP does not currently exercise. For a selected release D: 1. The Platform resolves one exact Business profile for D, either the current profile or a supported_versions leaf whose ucp.version matches its map key. 2. ucp.version D selects the UCP core snapshot published under D. 3. Every UCP-authored service, capability, and extension declaration in that profile carries D. Because services[name][] pairs one service with one transport binding, each binding entry repeats the service version. 4. Exact capability intersection selects the active capabilities and extensions from the chosen snapshot. 5. requires.protocol and requires.capabilities verify the selected versions; they do not select replacements. Transport descriptions remain versioned release artifacts. OpenAPI and OpenRPC info.version identifies the artifact published under D, while the version repeated in a service entry identifies the service. Under the current atomic contract, a transport binding does not introduce a separate negotiated version. Retain the individual version fields. They preserve the existing wire shape and leave room for UCP to define independently versioned core components in a future contract without requiring a profile migration. Today, those fields explicitly record the version of the selected snapshot rather than creating independent release axes. Keep independently authored contracts independent. Third-party extensions and payment handlers retain their author-controlled versions and release cadence. Scope build-time version injection to dev.ucp.* schemas so this ownership boundary is explicit without changing current generated output. Give each part of the model one authoritative home: Protocol Version owns exact profile discovery, Capability Versions owns exact intersection, Component Versioning and Release Snapshots owns D alignment, and the versioning guide owns release maintenance. Approved backwards-compatible changes may be backported and the snapshot re-certified; breaking changes enter a new release. This is a contract clarification, not a wire migration. It changes no profile shape, required field, payload validation, or current generated artifact; the schema edits are descriptions only. It also clarifies that dev.ucp.* capabilities are governed by the UCP Tech Council responsible for their domain.
jingyli
left a comment
There was a problem hiding this comment.
Thanks for proposing the clarification! Given this PR, it would also significantly simplify version's correlation to UCP's releases (related RFC - #499), especially if the releases are going to be done at a fixed/regular cadence.
But curious if we have some early thoughts around: How will the first release for new services look like? Let's hypothetically say a new service (i.e. food ordering) has an approved spec tomorrow, will they need to wait until the next official release of UCP to stamp a version D on their capabilities? Or will these be considered as backwards-compatible changes (since they are net new capabilities) and be backported into a previous stable release?
| it, and UCP re-certifies the snapshot before publishing the updated artifacts. | ||
| Breaking changes are never backported; they enter the next release. |
There was a problem hiding this comment.
Trying to pressure test this statement here: I do agree with it for almost all of the cases we will face but do we want to give us some break-glass mechanism to override this (subject to Governance Council's discretion on a case-by-case evaluation) when there is a sensitive breaking fix that needs to be backported into a previous release?
Example from the v2026-04-08 release: #441
| capabilities and extensions are still selected by exact-version intersection (see | ||
| [Capability Versions](#capability-versions)). | ||
|
|
||
| #### Third-party extensions (`com.{vendor}.*`, `org.{org}.*`) |
There was a problem hiding this comment.
With this new language, we are now limiting the scope of third-party/vendor's participation to just extensions (vs. before it was generalized to capabilities). Is this reduction in vendor scope intentional?
In my mind, the main pros and cons will be:
- (+): Tightening the usage so that we ensure UCP prescribes the main building blocks (base capabilities) and anything vendors introduce is only supplemental to these main building blocks
- (-): Removing the flexibility for implementors to "mix-and-match" - i.e. businesses & platforms cannot mutually agree to negotiate for a vendor catalog capability and UCP's checkout capability
My gut feeling is that the con above will eliminate some interesting use cases - especially for base capabilities that UCP has not yet defined (i.e. if a vendor wants to temporarily lean on their own identity management capability before some flavour of #337 lands in UCP).
| intersection algorithm considers only capability versions supported | ||
| by both parties. | ||
| Capability compatibility is established only by exact version equality, not by | ||
| inferring compatibility from date order. Each capability in the profile is an |
There was a problem hiding this comment.
Trying to visualize how this works in practice, especially cross-referencing against our new guidance on UCP-authored releases (please correct me if anything deviates from how you are thinking about it):
Scenario 1 - valid ✅
{
"ucp": {
"version": "2026-04-08",
"supported_versions": {
"2026-01-23": "https://business.example.com/.well-known/ucp/2026-01-23"
}
"services": {...},
"capabilities": {
"dev.ucp.shopping.checkout": [
{
"version": "2026-04-08",
"spec": "https://ucp.dev/2026-04-08/specification/checkout",
"schema": "https://ucp.dev/2026-04-08/schemas/shopping/checkout.json"
}
],
},
....
}
Scenario 2 - valid ✅
{
"ucp": {
"version": "2026-04-08",
"supported_versions": {
"2026-01-23": "https://business.example.com/.well-known/ucp/2026-01-23"
}
"services": {...},
"capabilities": {
"dev.ucp.shopping.checkout": [
{
"version": "2026-04-08",
"spec": "https://ucp.dev/2026-04-08/specification/checkout",
"schema": "https://ucp.dev/2026-04-08/schemas/shopping/checkout.json"
},
{
"version": "2026-01-23",
"spec": "https://ucp.dev/2026-01-23/specification/checkout",
"schema": "https://ucp.dev/2026-01-23/schemas/shopping/checkout.json"
}
],
},
....
}
Scenario 3 - invalid ❌
{
"ucp": {
"version": "2026-04-08",
"supported_versions": {
"2026-01-23": "https://business.example.com/.well-known/ucp/2026-01-23"
}
"services": {...},
"capabilities": {
"dev.ucp.shopping.cart": [
{
"version": "2026-04-08",
"spec": "https://ucp.dev/2026-04-08/specification/cart",
"schema": "https://ucp.dev/2026-04-08/schemas/shopping/cart.json"
}
],
"dev.ucp.shopping.checkout": [
{
"version": "2026-01-23",
"spec": "https://ucp.dev/2026-01-23/specification/checkout",
"schema": "https://ucp.dev/2026-01-23/schemas/shopping/checkout.json"
}
],
},
....
}
Or is it the case that Scenario 3 would also be valid - i.e. the sentence UCP-authored dev.ucp.* entries declare version D in release D only applies to the release, and not on how the profile can be built?
| 4. Otherwise: the business does not support the platform's protocol | ||
| 2. If the Platform's protocol version matches `version`, it uses this | ||
| profile directly and proceeds to capability negotiation. | ||
| 3. If the Platform's protocol version is a key in |
There was a problem hiding this comment.
Trying to poke this sentence a bit more, especially approaching it from the angle where platforms may also have multiple supported protocol versions: what this is indicating is that negotiation's scope is limited to that mapped URI being fetched (and there should ever be only one profile being "selected" for negotiation) and there should be no cross-profile "merging" happening. <- is this correct?
This would mean let's say if a business advertises cart capability only in their v2026-04-08 profile (which has supported_versions containing v2026-01-23) and checkout capability in their v2026-01-23 profile, then from platform's perspective (if both versions are supported), only cart or checkout capability can be negotiated (and not both via running intersection algorithms against both profiles)?
The only way to achieve negotiating both capabilities would be to have both capabilities (cart on 2026-04-08 version and checkout on 2026-01-23 version) advertised in the same v2026-04-08 profile?
UCP intentionally gave services, capabilities, and extensions their own version fields so they could evolve independently from
ucp.version. The specification described that flexibility as part of the protocol model.In practice, UCP's publishing and certification process settled on an atomic release contract: each dated core release ships its services and transport bindings, capabilities, extensions, and shared schemas as one tested snapshot. This PR codifies that current contract rather than continuing to imply independent release schedules that UCP does not currently exercise.
The release contract
For a selected release
D:D, either the current profile or asupported_versionsleaf whoseucp.versionmatches its map key.ucp.version = Dselects the UCP core snapshot published underD.D.requires.protocolandrequires.capabilitiesverify the selected versions; they do not select replacements.Transport descriptions remain versioned release artifacts. OpenAPI and OpenRPC
info.versionidentifies the artifact published underD; it is not another profile negotiation axis. This does not prevent transport specifications from evolving—it defines how UCP currently bundles them into a core release.The individual version fields remain in place. They preserve the existing wire shape and leave room for UCP to define independently versioned core components in a future contract without requiring a profile migration.
Independent contracts
Third-party extensions and payment handlers retain their author-controlled versions and release cadence. The build hook now stamps the release version only onto UCP-authored
dev.ucp.*schemas, making that ownership boundary explicit without changing current generated output. The PR also clarifies thatdev.ucp.*capabilities are governed by the UCP Tech Council responsible for their domain.Documentation (re)structure
To avoid repeating subtly different rules across the documentation:
supported_versions.Dalignment and version ownership.Compatibility
This is a contract clarification, not a wire migration.
Related Issues
Closes #499. Supersedes #418.
Checklist