From d6abd3e5079d03b5646d0c052eda2e1b7db6dbd8 Mon Sep 17 00:00:00 2001 From: Ilya Grigorik Date: Sun, 19 Jul 2026 23:28:38 -0400 Subject: [PATCH 1/6] clarify UCP core release versioning contract 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. --- docs/documentation/core-concepts.md | 81 ++++----- docs/documentation/schema-authoring.md | 78 ++++++--- docs/specification/overview.md | 219 ++++++++++++++++--------- docs/versioning.md | 33 ++-- hooks.py | 17 +- source/schemas/service.json | 2 +- source/schemas/ucp.json | 4 +- 7 files changed, 273 insertions(+), 161 deletions(-) diff --git a/docs/documentation/core-concepts.md b/docs/documentation/core-concepts.md index da07d1937..4127a76e7 100644 --- a/docs/documentation/core-concepts.md +++ b/docs/documentation/core-concepts.md @@ -127,16 +127,17 @@ interact. ### Capabilities -Capabilities are standalone, independently versioned features that -a business declares it supports. They are the "verbs" of the protocol — -discrete units of functionality that platforms can discover, negotiate, and -invoke. +Capabilities are discrete, versioned features that a Business declares it +supports. They are the "verbs" of the protocol — units of functionality that +Platforms can discover, negotiate, and invoke. Each capability is identified by a reverse-domain name (e.g., `dev.ucp.shopping.checkout`) and carries a date-based version. Capabilities -are declared in the business's UCP profile at `/.well-known/ucp` and -negotiated and confirmed in every response so that the platform always knows -the active feature set for a given interaction. +are declared in the Business's UCP profile at `/.well-known/ucp`, negotiated +by exact version, and confirmed in every response so that the Platform always +knows the active feature set for a given interaction. UCP-authored `dev.ucp.*` +capabilities and extensions declare version `D` in each UCP release `D`. +Third-party extensions publish versions on their own cadence. The following are examples of capabilities defined in UCP — see the [Specification](../specification/overview.md) for the authoritative and @@ -190,9 +191,9 @@ up-to-date list. ### Services **Services** group the operations and events for a vertical under a -reverse-domain namespace (e.g., `dev.ucp.shopping`). -A service declares *what* functionality exists for that vertical; transport -bindings declare *how* it is accessed on the wire. +reverse-domain registry key (e.g., `dev.ucp.shopping`). The key identifies the +service: a service declares *what* functionality exists for that vertical, and +each entry in its array declares *how* it is accessed over a transport binding. A single service can be accessed via multiple transport bindings: @@ -203,12 +204,15 @@ A single service can be accessed via multiple transport bindings: | **A2A** | Agent Card | Agent-to-Agent protocol integrations | | **Embedded** | OpenRPC | Embedded integrations | -A business declares which transport bindings it supports within each service; -platforms pick whichever fits their context — an AI agent may prefer MCP, a -traditional web app may use REST. +A Business declares which transport bindings it supports within each service; +Platforms pick whichever fits their context — an AI agent may prefer MCP, a +traditional web app may use REST. Every UCP-defined service declares an explicit +`version` equal to the selected `ucp.version`; because each entry pairs the +service with one transport binding, that service `version` repeats on each entry. +Transport bindings have no separate version. Multiple UCP-defined vertical +services can coexist under one `ucp.version`, each declaring that version. -Service namespaces are also UCP's extensibility mechanism for new -verticals — e.g., `dev.ucp.hotels` may be introduced in the future. +Service namespaces are also UCP's extensibility mechanism for new verticals. Businesses opt in by declaring which services they support. ## Discovery & Capability Negotiation @@ -335,13 +339,13 @@ reject entities that fail it. Identifiers carry no fetched URL, and the `spec` normative algorithm. The `dev.ucp.*` namespace is reserved exclusively for capabilities governed by -the UCP Tech Council. Any vendor can define and publish capabilities under their -own domain — `org.acme.*` — without UCP maintainer approval. Vendor -capabilities follow the same extension model, meaning they can extend UCP base -capabilities (e.g., `org.acme.loyalty` extending `dev.ucp.shopping.checkout`) -or define entirely new ones. Because negotiation is always opt-in, vendor -capabilities only activate when both parties declare them, keeping the protocol -decentralized by design. +the UCP Tech Council responsible for the capability's domain. Any vendor can +define and publish capabilities under their own domain — `org.acme.*` — without +UCP maintainer approval. Vendor capabilities follow the same extension model, +meaning they can extend UCP base capabilities (e.g., `org.acme.loyalty` +extending `dev.ucp.shopping.checkout`) or define entirely new ones. Because +negotiation is always opt-in, vendor capabilities only activate when both parties +declare them, keeping the protocol decentralized by design. ## Payment Architecture @@ -437,20 +441,19 @@ Businesses publish their OAuth 2.0 server metadata at ## Versioning -UCP uses date-based version identifiers (`YYYY-MM-DD`). The version represents -the date of the last backwards-incompatible change. - -* **Non-breaking additions** do not increment the date. -* **Breaking changes** require a `!` prefix in the PR title and a 2-week - advance notice to the community before merging. -* Businesses that support older protocol versions **SHOULD** publish - version-specific profiles and advertise them via the `supported_versions` - field in their profile, enabling platforms to discover the exact capability - set for each supported version. - -Capability schemas carry their version inline, which enables independent -versioning — a discount extension can version on a different cadence than -the checkout capability it extends. - -See [Versioning](../versioning.md) for the release branch process and the -full breaking-change procedure. +UCP uses date-based version identifiers (`YYYY-MM-DD`). A UCP release `D` is a +snapshot of the core protocol — its services and transport bindings, +capabilities, extensions, and shared schemas — published and certified together +as internally compatible. The selected `ucp.version` selects that snapshot; +selection is exact, so an older date is available only when the Business +advertises it in `supported_versions`. + +Every UCP-defined service, capability, and extension in release `D` declares +version `D`. Capabilities and extensions are selected by exact-version +intersection. Third-party extensions and payment handlers version independently +on their authors' own schedules. + +See [Protocol Version](../specification/overview.md#protocol-version) for version +discovery, [Component Versioning and Release Snapshots](../specification/overview.md#component-versioning-and-release-snapshots) +for the normative release contract, and [Versioning](../versioning.md) for the +release-branch and backport process. diff --git a/docs/documentation/schema-authoring.md b/docs/documentation/schema-authoring.md index cf16a6026..8b6952aec 100644 --- a/docs/documentation/schema-authoring.md +++ b/docs/documentation/schema-authoring.md @@ -29,29 +29,35 @@ UCP schemas use standard JSON Schema fields plus UCP-specific metadata: | `$id` | JSON Schema | Schema's canonical URI for `$ref` resolution | All schemas | | `title` | JSON Schema | Human-readable display name | All schemas | | `description` | JSON Schema | Schema purpose and usage | All schemas | -| `name` | UCP | Reverse-domain identifier; doubles as registry key | Capabilities, services, handlers | -| `version` | UCP | Entity version (`YYYY-MM-DD` format) | Capabilities, services, payment handlers | +| `name` | UCP | Reverse-domain identifier; doubles as registry key | Capabilities, extensions, payment handlers | +| `version` | UCP | Entity version (`YYYY-MM-DD` format) | Capabilities, extensions, payment handlers | | `id` | UCP | Instance identifier for multiple configurations | Payment handlers only | +This table lists top-level metadata embedded in published schema documents; +profile registry declarations are covered in the sections below. + ### Why Self-Describing? -Capability schemas **must be self-describing**: when a platform fetches a schema, -it should determine exactly what capability and version it represents without -cross-referencing other documents. This matters because: +Capability and payment-handler schemas **must be self-describing**: when a +Platform fetches a schema, it should determine exactly what entity and version +it represents without cross-referencing other documents. This matters because: -1. **Independent versioning**: Capabilities may version independently. The schema - must declare its version explicitly—you can't infer it from the URL. +1. **Explicit version identity**: Capabilities, extensions, and payment handlers + declare their version explicitly; it cannot be inferred from the URL. + UCP-authored `dev.ucp.*` capabilities and extensions declare version `D` in + each UCP release `D`. Payment handlers publish on their authors' own schedules. 2. **Validation**: Validators can cross-check that a capability declaration's `schema` URL points to a schema whose embedded `name`/`version` match the declaration. Mismatches are authoring errors caught at build time. 3. **Developer experience**: When reading a schema file, integrators immediately - see what capability it defines without reverse-engineering the `$id` URL. + see what entity it defines without reverse-engineering the `$id` URL. 4. **Compact namespace**: The `name` field provides a standardized reverse-domain identifier (e.g., `dev.ucp.shopping.checkout`) that's more compact and semantic - than the full `$id` URL. + than the full `$id` URL. Service registry keys provide the same stable + identity. ### Why Both `$id` and `name`? @@ -77,9 +83,10 @@ breaking capability negotiation. The `version` field uses date-based versioning (`YYYY-MM-DD`) to enable: - **Capability negotiation**: Platforms request specific versions they support -- **Breaking change management**: New versions get new dates; old versions remain - valid and resolvable -- **Independent lifecycles**: Extensions can release on their own schedule +- **Certified release identity**: Each date identifies the entity's complete, + published schema closure +- **Independent author lifecycles**: Third-party extensions and payment handlers + can release on their authors' own schedules ## Schema Categories @@ -99,7 +106,14 @@ Examples: `checkout.json`, `fulfillment.json`, `discount.json`, `order.json` Define transport bindings that appear in `ucp.services{}` registries. Each transport (REST, MCP, A2A, Embedded) is a separate entry. -- **Top-level fields**: `$schema`, `$id`, `title`, `description`, `name`, `version` +- **Registry identity**: Reverse-domain service name used as the registry key +- **Entry fields**: `version`, `spec`, `schema`, `config`, and transport-specific fields +- **Versioning**: Every service entry declares an explicit `version`; in release + `D` it is `D`. Because each entry pairs the service with one transport binding, + that service `version` repeats on each entry, and transport bindings have no + separate version. The referenced OpenAPI/OpenRPC artifact carries its own + `info.version` as artifact metadata, not a negotiated version. See + [Component Versioning and Release Snapshots](../specification/overview.md#component-versioning-and-release-snapshots) - **Variants**: `platform_schema`, `business_schema` - **Transport requirements** (additional beyond the common base): - Platform profile (`platform_schema`): REST/MCP/Embedded require `schema` (OpenAPI/OpenRPC URL). A2A has no additional requirements. @@ -178,11 +192,11 @@ by `name` rather than arrays of objects with `name` fields. The same registry structure appears in three contexts with different field requirements: -| Context | Location | Required Fields | -| ------- | -------- | --------------- | -| Platform Profile | Advertised URI | `version`, `spec`, `schema` | -| Business Profile | `/.well-known/ucp` | `version`; may add `config` | -| API Responses | Checkout/order payloads | `version` (+ `id` for handlers) | +| Registry | Platform Profile | Business Profile | API Responses | +| -------- | ---------------- | ---------------- | ------------- | +| Services | `version`, `transport`, `spec`; `schema` for REST, MCP, and Embedded | `version`, `transport`; `endpoint` for REST, MCP, and A2A | `version`, `transport`; transport-specific `config` where applicable | +| Capabilities/extensions | `version`, `spec`, `schema` | `version`, `schema`; may add `config` | `version` | +| Payment handlers | `id`, `version`, `spec`, `schema` | `id`, `version`; may add `config` | `id`, `version` | ## The Entity Pattern @@ -288,14 +302,30 @@ values documented in the `description`. ## Versioning Strategy +### UCP Services (`dev.ucp.*`) + +In each UCP release `D`, every UCP-defined service entry declares version `D`. +See [Service Schemas](#service-schemas) for how the flattened registry represents +service versions and transport bindings. + ### UCP Capabilities (`dev.ucp.*`) -UCP-authored capabilities version with protocol releases by default. Individual -capabilities **may** version independently when needed. +In each UCP release `D`, every UCP-defined capability and extension declares +version `D`, even when its schema did not change directly. Declaring version `D` +does not replace negotiation: capabilities and extensions are still selected by +exact-version intersection. + +Profile selection, including profiles for older supported releases, is defined +in [Protocol Version](../specification/overview.md#protocol-version). + +### Third-Party Extensions and Payment Handlers -### Vendor Capabilities (`com.{vendor}.*`) +Third-party extensions and payment handlers publish versions on their authors' +own schedules. Their versions remain independent of the selected `ucp.version` +and are not constrained to `D`. UCP's `payment_handler.json` defines the shared +declaration shape, not a payment-handler implementation or its release cadence. -Capabilities outside `dev.ucp.*` version fully independently: +Third-party extensions version independently: ```json @@ -307,7 +337,9 @@ Capabilities outside `dev.ucp.*` version fully independently: } ``` -Vendor schemas follow the same self-describing requirements. +Extension version requirements belong in the extension schema document: +`requires.protocol` constrains the selected `ucp.version`, and +`requires.capabilities` constrains selected parent versions. ## Extensibility and Forward Compatibility diff --git a/docs/specification/overview.md b/docs/specification/overview.md index 452e7a5b7..225ee3b77 100644 --- a/docs/specification/overview.md +++ b/docs/specification/overview.md @@ -33,21 +33,14 @@ Schema notes: ## Discovery, Governance, and Negotiation -UCP separates protocol version compatibility from capability negotiation. -The business's profile at `/.well-known/ucp` describes capabilities for -the protocol version it declares. Businesses that support older protocol -versions **SHOULD** publish version-specific profiles and advertise them -via the `supported_versions` field — a map from protocol version to -profile URI, enabling platforms to discover the exact capabilities for a -specific protocol version. Version lifecycle, including when to deprecate -or remove older versions from `supported_versions`, is a business policy -decision. The protocol does not prescribe a deprecation schedule. -Capability negotiation follows a server-selects architecture where the -business (server) determines the active capabilities from the -intersection of both parties' declared capabilities. Both business and -platform profiles can be cached by both parties, allowing efficient -capability negotiation within the normal request/response flow between -platform and business. +UCP separates [protocol version selection](#protocol-version) from +[capability negotiation](#capability-versions). A Business advertises its current +protocol version and links to profiles for older supported versions. After the +Platform selects one exact version, the Business determines the active +capabilities from the versions both parties advertise. Version lifecycle, +including when to remove an older version, is a Business policy decision; UCP +does not prescribe a deprecation schedule. Business and Platform profiles can be +cached by both parties. ### Namespace Governance @@ -203,6 +196,15 @@ standard formats: - **A2A**: Agent Card Specification - **EP(embedded)**: OpenRPC (JSON format) +A service is identified by its reverse-domain registry key (e.g., +`dev.ucp.shopping`). In a profile, services are keyed by that name, and each +entry in `services[name][]` pairs the service with one transport binding and +declares the service `version`: in release `D` that version is `D`. This is the +service version, not a transport version — the binding has no separate version. +The OpenAPI or OpenRPC artifact a binding references carries its own +`info.version` as release metadata, not a separate version to negotiate. See +[Component Versioning and Release Snapshots](#component-versioning-and-release-snapshots). + #### Service Definition {{ extension_schema_fields('service.json#/$defs/platform_schema', 'overview') }} @@ -357,9 +359,14 @@ This convention ensures: ##### Version Requirements -Extension schemas **SHOULD** declare a `requires` object (alongside -`name`, `title`, `description`) to indicate the protocol and -capability versions required for correct operation: +Extension authors **SHOULD** declare a `requires` object in the extension +schema (alongside its `name`, `title`, and `description`) stating the versions +the extension depends on. A third-party extension schema declares its own +author-controlled `version`, which advances independently of `ucp.version`; +a UCP-authored `dev.ucp.*` extension declares version `D` in release `D`. +`requires.protocol` constrains the selected `ucp.version`, and +`requires.capabilities` constrains the selected versions of the named +capabilities: ```json @@ -367,6 +374,7 @@ capability versions required for correct operation: "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://acme.com/ucp/schemas/loyalty.json", "name": "com.acme.shopping.loyalty", + "version": "2026-06-15", "title": "Acme Loyalty Points", "requires": { "protocol": { "min": "2026-01-23" }, @@ -399,13 +407,16 @@ no upper bound: ``` Keys in `requires.capabilities` **MUST** be a subset of the -extension's `$defs` keys. If `requires` is present, platforms and -businesses **MUST** verify the negotiated protocol version and -capability versions satisfy the declared constraints during schema -resolution. Incompatible extensions are excluded from the active -capability set (see [Resolution Flow](#resolution-flow)). If -`requires` is absent, the extension is assumed to be compatible -with the versions declared by the profile. +extension's `$defs` keys. These ranges verify dependencies after exact +versions are selected; they do not select versions. The `ucp.version` is +fixed first by [profile selection](#protocol-version) and capability versions +by the [intersection algorithm](#intersection-algorithm); then, if `requires` +is present, Platforms and Businesses **MUST** verify that the selected +`ucp.version` and capability versions satisfy the declared constraints during +schema resolution. Incompatible extensions are excluded from the active +capability set (see [Resolution Flow](#resolution-flow)). If `requires` is +absent, the extension is assumed to be compatible with the versions declared +by the profile. #### Schema Resolution Convention @@ -2195,12 +2206,13 @@ implementation guide, and examples. UCP uses date-based versioning in the format `YYYY-MM-DD`. This provides clear chronological ordering and unambiguous version comparison. -### Version Discovery and Negotiation - -UCP prioritizes strong backwards compatibility. Businesses implementing a -version **SHOULD** handle requests from platforms using that version or older. +### Versioned Profiles -Both businesses and platforms declare a single version in their profiles: +UCP prioritizes strong backwards compatibility. Each profile document declares +one core release version in `ucp.version`; its services, capabilities, +extensions, and payment handlers also carry explicit versions. A Business +advertises its current profile and links to profiles for older supported releases +through `supported_versions`; see [Protocol Version](#protocol-version). #### Example @@ -2232,16 +2244,15 @@ Both businesses and platforms declare a single version in their profiles: } ``` -### Version Negotiation +### Version Selection ![High-level resolution flow sequence diagram](site:specification/images/ucp-discovery-negotiation.png) -Version compatibility operates at two levels: the **protocol version** -and **capability versions**. The protocol version (`ucp.version`) -governs core protocol mechanisms — discovery, negotiation flow, -transport bindings, and signature requirements. Capability versions -govern the semantics of each feature independently, as defined in -[Independent Component Versioning](#independent-component-versioning). +Protocol version selection chooses one complete Business profile first. +Capability negotiation then selects active capabilities and extensions by exact +version intersection within that profile. The sections below define each step; +see [Component Versioning and Release Snapshots](#component-versioning-and-release-snapshots) +for release alignment and version ownership. #### Protocol Version @@ -2252,9 +2263,9 @@ and payment handlers available at that version. Businesses that support older protocol versions **SHOULD** declare a `supported_versions` object mapping each older version to a profile URI. Each URI points to a complete, self-contained profile for that -version — including its own capabilities, services, payment handlers, -and signing keys. When `supported_versions` is omitted, only -`version` is supported. +version — whose `ucp.version` equals its map key and which includes +its own capabilities, services, payment handlers, and signing keys. +When `supported_versions` is omitted, only `version` is supported. ```json @@ -2274,17 +2285,20 @@ and signing keys. When `supported_versions` is omitted, only Platforms discover a business's capabilities through the following flow: -1. Platform fetches `/.well-known/ucp` — this is the current version +1. The Platform fetches `/.well-known/ucp` — this is the current version profile. -2. If the platform's protocol version matches `version`: use this - profile directly. Proceed to capability negotiation. -3. If the platform's protocol version is a key in - `supported_versions`: fetch the profile at the mapped URI. This - profile describes the capabilities available at that protocol - version. Proceed to capability negotiation. -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 + `supported_versions`: fetch the profile at the mapped URI. The + Platform **MUST** verify that the fetched profile's `ucp.version` + equals the `supported_versions` key it selected; on mismatch the + Platform **MUST NOT** use that profile. Otherwise this profile + describes the capabilities available at that protocol version — + proceed to capability negotiation. +4. Otherwise, the Business does not support the Platform's protocol version. Platforms **SHOULD NOT** send requests with an incompatible - version; businesses **MUST** respond with a `version_unsupported` + version; Businesses **MUST** respond with a `version_unsupported` error. Version-specific profiles are leaf documents — they describe exactly @@ -2360,12 +2374,17 @@ notice. #### Capability Versions -Capability versions are negotiated independently of the protocol -version. Each capability in the profile is an array. Multiple entries -for the same capability, each with a different `version`, advertise -support for multiple versions of that capability. The capability -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 +array; multiple entries with different `version` values advertise support for +multiple versions where the applicable publication policy permits it. When the +exact shared set contains more than one version, the intersection algorithm +orders those shared dates to select the latest one. Supported third-party +extension versions advance independently of `ucp.version`. UCP-authored +`dev.ucp.*` entries declare version `D` in release `D`: a profile for +`ucp.version = D` advertises version `D` for those entries. The capability +intersection algorithm considers only capability versions supported by both +parties. Businesses **MUST** include only capabilities compatible with the negotiated protocol version in their response. A capability that @@ -2374,9 +2393,20 @@ NOT** be included when processing at an older protocol version. ### Backwards Compatibility +UCP classifies changes as backwards-compatible or breaking and applies this +classification to its own components to govern how a UCP release advances. The +lists below describe which changes preserve and which break conforming +integrations; third-party extension and payment-handler authors control their own +version policy and can apply the same distinction on their own cadence. + #### Backwards-Compatible Changes -The following changes **MAY** be introduced without a new version: +The following changes are **backwards-compatible**: they do not break conforming +integrations. UCP publishes backwards-compatible features in the next official +UCP release, which advances the version date, and **MAY** also backport an +approved backwards-compatible change — a feature, or a security, correctness, or +interoperability fix — to an already-published supported release. The +backwards-compatible changes are: - Adding new non-required fields to responses - Adding new non-required parameters to requests @@ -2388,7 +2418,9 @@ The following changes **MAY** be introduced without a new version: #### Breaking Changes -The following changes **MUST NOT** be introduced without a new version: +The following changes are **breaking**: they break conforming integrations and +require a new component version. UCP introduces a breaking change only in a new +dated release, never as a backport to an already-published release. - Removing or renaming existing fields - Changing field types or semantics @@ -2398,26 +2430,59 @@ The following changes **MUST NOT** be introduced without a new version: - Modifying existing protocol flow or state machine - Changing the meaning of existing error codes -### Independent Component Versioning - -- UCP protocol versions independently from capabilities. -- Each capability versions independently from other capabilities. -- Capabilities **MUST** follow the same backwards compatibility rules as the - protocol. -- Businesses **MUST** validate capability version compatibility using the same - logic as what's described above. -- Transports **MAY** define their own version handling mechanisms. - -#### UCP Capabilities (`dev.ucp.*`) - -UCP-authored capabilities version with protocol releases by default. Individual -capabilities **MAY** version independently when breaking changes are required -outside the protocol release cycle. - -#### Vendor Capabilities (`com.{vendor}.*`) - -Capabilities outside the `dev.ucp.*` namespace version fully independently. -Vendors control their own release schedules and versioning strategy. +### Component Versioning and Release Snapshots + +A UCP release `D` is a snapshot of the core protocol — its services and transport +bindings, capabilities, extensions, and shared schemas — published and certified +together as internally compatible. Selecting `ucp.version` `D` selects that +snapshot. For a release `D`: + +1. `ucp.version` is `D`. +2. Every UCP-defined service declares `version` `D`. Each service entry pairs the + service with one transport binding, so every entry under a service repeats + that service `version`; transport bindings have no separate version. The + referenced OpenAPI/OpenRPC artifacts are published under `D` as release + metadata. +3. Every UCP-defined capability and extension declares `version` `D`, even when + its own schema did not change directly. +4. Shared schemas are published as part of the same snapshot. +5. UCP certifies the snapshot — components, bindings, and supported + compositions — together before publishing it. + +Breaking changes are not backported to an earlier release; they enter the next +release. UCP **MAY** backport an approved backward-compatible change — a feature, +or a security, correctness, or interoperability fix — to a supported release and +its generated `D` artifacts, and **MUST** re-certify the snapshot before +publishing the updated artifacts. + +A Business or Platform that selects `ucp.version` `D` **MUST** declare version +`D` on every `dev.ucp.*` service, capability, and extension entry in its +profile. An older release is selected only through a separate +`supported_versions` leaf profile whose own `ucp.version` is that older release +date (see [Protocol Version](#protocol-version)). Payment-handler versions remain +independently controlled by their authors and are not constrained +to `D`. Declaring `version` `D` does not change negotiation: `dev.ucp.*` +capabilities and extensions are still selected by exact-version intersection (see +[Capability Versions](#capability-versions)). + +#### Third-party extensions (`com.{vendor}.*`, `org.{org}.*`) + +Third-party authors participate by extending UCP-defined root capabilities +through independently versioned **extensions**. A third-party extension: + +- declares `extends` over one or more UCP-defined root capabilities, composed + via `allOf` (see [Extension Schema Pattern](#extension-schema-pattern)); +- uses the UCP-defined services and transport bindings selected by + `ucp.version`; +- advertises exact extension versions in Business and Platform profiles and is + negotiated by exact-version intersection like every capability; and +- is published on its author's own cadence, independent of UCP release dates. + +A third-party extension's version is never tied to `ucp.version`. The author +declares the UCP releases and capability versions the extension needs through +`requires` (see [Version Requirements](#version-requirements)); those ranges +verify compatibility with the versions a profile selects, they do not select +versions. ## Glossary diff --git a/docs/versioning.md b/docs/versioning.md index 5366b5017..4aa97f97e 100644 --- a/docs/versioning.md +++ b/docs/versioning.md @@ -1,27 +1,36 @@ # Versioning -UCP uses date-based version identifiers following the format `YYYY-MM-DD` to -indicate the last date backwards incompatible changes were made. +This guide defines UCP's release-branch and backport process. UCP uses date-based +version identifiers in `YYYY-MM-DD` format; see +[Component Versioning and Release Snapshots](specification/overview.md#component-versioning-and-release-snapshots) +for the normative release model and +[Protocol Version](specification/overview.md#protocol-version) for profile +selection. -New development occurs on the `main` branch. We will maintain long-lived -branches for all supported releases of the spec. +New development occurs on the `main` branch. We maintain long-lived branches for +all supported releases of the spec so that a published snapshot `D` stays +available for reference and maintenance. Approved backward-compatible changes — +features as well as security, correctness, and interoperability fixes — may be +backported to a maintained `release/D` branch and the artifacts generated from +it, and UCP re-certifies the snapshot before publishing the updated artifacts. +Breaking changes are never backported; they enter the next release. * When the Tech Council approves a new version of UCP, we will cut a new branch named `release/YYYY-MM-DD` directly from the current state of `main`. * We will implement a code freeze on the release branch the moment a - `release/YYYY-MM-DD` branch is cut. Only critical bug fixes should move - during this window. - * Critical issues discovered after cutting a release branch should be fixed + `release/YYYY-MM-DD` branch is cut. Only changes permitted by the policy + above should move during this window. + * Approved backward-compatible changes discovered after cutting a release branch should be made in one of two ways: - 1. The fix is made on the release branch and merged to `main`. - 2. The fix is made on `main` and cherry-picked to the release branch. + 1. The change is made on the release branch and merged to `main`. + 2. The change is made on `main` and cherry-picked to the release branch. * Once finalized, we will merge the release branch into `main` and tag it (e.g., `git tag -a vYYYY-MM-DD`). We will use a GitHub Action to detect the new tag and automatically generate a release notes draft and upload artifacts. -* Unlike temporary feature branches, release/YYYY-MM-DD branches are long-lived - and correspond to specific versions of the spec for historical reference and - maintenance. +* Unlike temporary feature branches, `release/YYYY-MM-DD` branches are + long-lived and correspond to specific versions of the spec for historical + reference and maintenance. ## Breaking PRs diff --git a/hooks.py b/hooks.py index 012d71d98..c52356a2f 100644 --- a/hooks.py +++ b/hooks.py @@ -123,15 +123,18 @@ def _rewrite_version_urls(data, url_version): def _set_schema_version(data, version): - """Set version field for named entities (capabilities, services, handlers). + """Set versions for versioned entities and transport artifacts. - Named entities (schemas with top-level 'name' field) require version per - ucp.json#/$defs/entity. Build injects version so source files don't need it. + UCP-authored capability and extension schemas require version per + ucp.json#/$defs/entity. Build injects the release version only for dev.ucp.* + schemas published in the core release. Third-party extensions and payment + handlers retain their author-controlled versions; the UCP payment-handler + meta-schema has no name and defines only the shared declaration structure. - Additionally, for OpenAPI and OpenRPC transport specifications, set the - required info.version field. + For OpenAPI and OpenRPC transport specifications, set the required + info.version field as release artifact metadata. """ - if "name" in data: + if str(data.get("name", "")).startswith("dev.ucp."): data["version"] = version if ("openapi" in data or "openrpc" in data) and isinstance( @@ -430,7 +433,7 @@ def on_post_build(config): # Step 1: Resolve relative $ref to absolute URLs _process_refs(data, src_file.parent) - # Step 2: Inject version field for named entities + # Step 2: Inject versions for versioned entities and transport artifacts if schema_version: _set_schema_version(data, schema_version) diff --git a/source/schemas/service.json b/source/schemas/service.json index 88f645698..880875145 100644 --- a/source/schemas/service.json +++ b/source/schemas/service.json @@ -2,7 +2,7 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://ucp.dev/schemas/service.json", "title": "UCP Service", - "description": "Service binding for a specific transport. Each transport binding is a separate entry in the service array.", + "description": "Service declaration with one transport binding. Each transport binding is a separate entry in the service array; `version` identifies the service, not the transport.", "$defs": { "base": { diff --git a/source/schemas/ucp.json b/source/schemas/ucp.json index 79716bee7..fdf11e72c 100644 --- a/source/schemas/ucp.json +++ b/source/schemas/ucp.json @@ -8,7 +8,7 @@ "version": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "description": "UCP version in YYYY-MM-DD format." + "description": "Version identifier in YYYY-MM-DD format." }, "version_constraint": { @@ -34,7 +34,7 @@ "properties": { "protocol": { "$ref": "#/$defs/version_constraint", - "description": "Required protocol version." + "description": "Required range for the selected `ucp.version`." }, "capabilities": { "type": "object", From 7d8b56bdf2e03b65aedf55eee471fcc86cd4d34e Mon Sep 17 00:00:00 2001 From: Ilya Grigorik Date: Mon, 27 Jul 2026 15:52:47 -0400 Subject: [PATCH 2/6] clarify mixed profile and breaking change policies - Breaking backports: next release by default, but the Governance Council MAY approve an exceptional backport to a supported release, following the breaking-change notice process and re-certification. - Consolidate backport policy into Component Versioning and Release Snapshots; Backwards Compatibility returns to pure classification and versioning.md keeps workflow mechanics with a pointer. Also removes the "never as a backport" sentence the escape hatch would have contradicted. - Mixed-version profiles: a profile for ucp.version D advertises exactly D for dev.ucp.* entries; older versions live only in supported_versions leaf profiles. The registry mechanism supports multi-version arrays, but the core contract exercises them only for third-party extensions. ucp-schema lint queued to enforce this. - Cross-profile negotiation: Platforms and Businesses MUST negotiate using exactly one selected profile and MUST NOT combine capabilities across profiles. - Third-party section reworded to versioning-only claims; whether vendors may define root capabilities is deferred to its own RFC. --- docs/specification/overview.md | 50 ++++++++++++++++++++-------------- docs/versioning.md | 15 +++++----- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/docs/specification/overview.md b/docs/specification/overview.md index b08cce3c7..2f90cb55e 100644 --- a/docs/specification/overview.md +++ b/docs/specification/overview.md @@ -2745,6 +2745,11 @@ Version-specific profiles are leaf documents — they describe exactly one protocol version and **MUST NOT** contain a `supported_versions` field. +Platforms and Businesses **MUST** negotiate using exactly one selected profile +and **MUST NOT** combine or intersect capabilities across profiles: a capability +is negotiable at a given protocol version only if it appears in that version's +profile. + ##### Request-Time Validation Businesses **MUST** validate the platform's protocol version on @@ -2822,7 +2827,12 @@ exact shared set contains more than one version, the intersection algorithm orders those shared dates to select the latest one. Supported third-party extension versions advance independently of `ucp.version`. UCP-authored `dev.ucp.*` entries declare version `D` in release `D`: a profile for -`ucp.version = D` advertises version `D` for those entries. The capability +`ucp.version = D` advertises version `D` for those entries. Older `dev.ucp.*` +versions are advertised only through `supported_versions` leaf profiles, never +as additional entries in a newer profile: the registry mechanism supports +multi-version arrays, but the core publication contract does not exercise them +for `dev.ucp.*` entries. Multi-version arrays therefore arise for third-party +extensions whose authors support multiple versions concurrently. The capability intersection algorithm considers only capability versions supported by both parties. @@ -2834,19 +2844,17 @@ NOT** be included when processing at an older protocol version. ### Backwards Compatibility UCP classifies changes as backwards-compatible or breaking and applies this -classification to its own components to govern how a UCP release advances. The -lists below describe which changes preserve and which break conforming +classification to its own components to govern how a UCP release advances. +Publication and backport policy for both classes is defined in +[Component Versioning and Release Snapshots](#component-versioning-and-release-snapshots). +The lists below describe which changes preserve and which break conforming integrations; third-party extension and payment-handler authors control their own version policy and can apply the same distinction on their own cadence. #### Backwards-Compatible Changes The following changes are **backwards-compatible**: they do not break conforming -integrations. UCP publishes backwards-compatible features in the next official -UCP release, which advances the version date, and **MAY** also backport an -approved backwards-compatible change — a feature, or a security, correctness, or -interoperability fix — to an already-published supported release. The -backwards-compatible changes are: +integrations. - Adding new non-required fields to responses - Adding new non-required parameters to requests @@ -2859,8 +2867,7 @@ backwards-compatible changes are: #### Breaking Changes The following changes are **breaking**: they break conforming integrations and -require a new component version. UCP introduces a breaking change only in a new -dated release, never as a backport to an already-published release. +require a new component version. - Removing or renaming existing fields - Changing field types or semantics @@ -2889,11 +2896,13 @@ snapshot. For a release `D`: 5. UCP certifies the snapshot — components, bindings, and supported compositions — together before publishing it. -Breaking changes are not backported to an earlier release; they enter the next -release. UCP **MAY** backport an approved backward-compatible change — a feature, -or a security, correctness, or interoperability fix — to a supported release and -its generated `D` artifacts, and **MUST** re-certify the snapshot before -publishing the updated artifacts. +UCP **MAY** backport an approved backward-compatible change — a feature, or a +security, correctness, or interoperability fix — to a supported release and its +generated `D` artifacts. Breaking changes enter the next release and are not +backported by default; in exceptional cases, the Governance Council **MAY** +approve backporting a breaking change to a supported release, following the +breaking-change notice process. UCP **MUST** re-certify the snapshot before +publishing any updated artifacts. A Business or Platform that selects `ucp.version` `D` **MUST** declare version `D` on every `dev.ucp.*` service, capability, and extension entry in its @@ -2907,16 +2916,17 @@ capabilities and extensions are still selected by exact-version intersection (se #### Third-party extensions (`com.{vendor}.*`, `org.{org}.*`) -Third-party authors participate by extending UCP-defined root capabilities -through independently versioned **extensions**. A third-party extension: +Third-party capabilities version independently of `ucp.version`: their authors +control both the version and the release cadence. The common form is an +**extension** over one or more UCP-defined root capabilities. A third-party +extension: - declares `extends` over one or more UCP-defined root capabilities, composed via `allOf` (see [Extension Schema Pattern](#extension-schema-pattern)); - uses the UCP-defined services and transport bindings selected by - `ucp.version`; + `ucp.version`; and - advertises exact extension versions in Business and Platform profiles and is - negotiated by exact-version intersection like every capability; and -- is published on its author's own cadence, independent of UCP release dates. + negotiated by exact-version intersection like every capability. A third-party extension's version is never tied to `ucp.version`. The author declares the UCP releases and capability versions the extension needs through diff --git a/docs/versioning.md b/docs/versioning.md index 4aa97f97e..9dadc2391 100644 --- a/docs/versioning.md +++ b/docs/versioning.md @@ -9,18 +9,19 @@ selection. New development occurs on the `main` branch. We maintain long-lived branches for all supported releases of the spec so that a published snapshot `D` stays -available for reference and maintenance. Approved backward-compatible changes — -features as well as security, correctness, and interoperability fixes — may be -backported to a maintained `release/D` branch and the artifacts generated from -it, and UCP re-certifies the snapshot before publishing the updated artifacts. -Breaking changes are never backported; they enter the next release. +available for reference and maintenance. Backport eligibility and approval — +approved backwards-compatible changes by default, breaking changes only through +exceptional Governance Council approval — are defined in +[Component Versioning and Release Snapshots](specification/overview.md#component-versioning-and-release-snapshots). +A backported change lands on the maintained `release/D` branch, and UCP +re-certifies the snapshot before publishing the updated artifacts. * When the Tech Council approves a new version of UCP, we will cut a new branch named `release/YYYY-MM-DD` directly from the current state of `main`. * We will implement a code freeze on the release branch the moment a - `release/YYYY-MM-DD` branch is cut. Only changes permitted by the policy - above should move during this window. + `release/YYYY-MM-DD` branch is cut. Only changes permitted by the backport + policy should move during this window. * Approved backward-compatible changes discovered after cutting a release branch should be made in one of two ways: 1. The change is made on the release branch and merged to `main`. From 6620c63665b94a4eba2226456c59e8fbdb3e8d5e Mon Sep 17 00:00:00 2001 From: Ilya Grigorik Date: Mon, 3 Aug 2026 07:49:29 -0700 Subject: [PATCH 3/6] clarify platform choice among supported protocol versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 2 now states that the Platform selects one mutually supported version from the Business's version and supported_versions keys, with a SHOULD-prefer-most-recent to keep the ecosystem moving forward — mirroring how capability intersection selects the latest shared date. Steps 3 and 4 follow from the selected version instead of "the Platform's protocol version", which is ambiguous once platforms support several. Selection remains exact and single-profile: this changes which profile may be chosen, not how many. --- docs/specification/overview.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/specification/overview.md b/docs/specification/overview.md index 2f90cb55e..e89287650 100644 --- a/docs/specification/overview.md +++ b/docs/specification/overview.md @@ -2727,19 +2727,22 @@ Platforms discover a business's capabilities through the following flow: 1. The Platform fetches `/.well-known/ucp` — this is the current version profile. -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 +2. The Platform selects a mutually supported protocol version from the + Business's `version` and `supported_versions` keys; Platforms **SHOULD** + prefer the most recent. If the selected version matches `version`, the + Platform uses this profile directly and proceeds to capability + negotiation. +3. If the selected version is a key in `supported_versions`: fetch the profile at the mapped URI. The Platform **MUST** verify that the fetched profile's `ucp.version` equals the `supported_versions` key it selected; on mismatch the Platform **MUST NOT** use that profile. Otherwise this profile describes the capabilities available at that protocol version — proceed to capability negotiation. -4. Otherwise, the Business does not support the Platform's protocol - version. Platforms **SHOULD NOT** send requests with an incompatible - version; Businesses **MUST** respond with a `version_unsupported` - error. +4. If no mutually supported version exists, the Business does not support + the Platform's protocol version. Platforms **SHOULD NOT** send requests + with an incompatible version; Businesses **MUST** respond with a + `version_unsupported` error. Version-specific profiles are leaf documents — they describe exactly one protocol version and **MUST NOT** contain a `supported_versions` From 88c4b4100e7d68ecad9e6542e092310ec0862c70 Mon Sep 17 00:00:00 2001 From: Ilya Grigorik Date: Tue, 4 Aug 2026 12:06:06 -0700 Subject: [PATCH 4/6] parallel negotiations across supported versions A selected profile guarantees compatibility among its declared capabilities at its declared version; resource representations may differ across versions. Platforms MAY run separate negotiations with the same Business at different supported versions, each bound to a single profile. --- docs/specification/overview.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/specification/overview.md b/docs/specification/overview.md index e89287650..232a0827e 100644 --- a/docs/specification/overview.md +++ b/docs/specification/overview.md @@ -2748,10 +2748,11 @@ Version-specific profiles are leaf documents — they describe exactly one protocol version and **MUST NOT** contain a `supported_versions` field. -Platforms and Businesses **MUST** negotiate using exactly one selected profile -and **MUST NOT** combine or intersect capabilities across profiles: a capability -is negotiable at a given protocol version only if it appears in that version's -profile. +A selected profile guarantees compatibility among its declared capabilities at +its declared version; across versions, resource representations may differ. +Platforms **MAY** run separate negotiations with the same Business at different +supported versions, and **SHOULD NOT** combine or intersect capabilities across +profiles within the same negotiation. ##### Request-Time Validation From dac71fb12f3a1868f41017cc174126f7f977f61d Mon Sep 17 00:00:00 2001 From: Ilya Grigorik Date: Wed, 5 Aug 2026 14:40:08 -0700 Subject: [PATCH 5/6] tighten versioning prose, scope backport exception MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review nits: reword core-concepts so dev.ucp.* components are versioned in lockstep with the specification — each declares the date D of the release it ships in — tighten the service-version sentence, and drop prose that duplicated the normative overview. Scope the Governance Council breaking-backport exception to defects that compromise the security, correctness, or interoperability of the release as published: releases are stable unless broken as shipped, rather than open to indefinite amendment. State the payment-handler clause as ownership-based — UCP defines the declaration structure but currently no handler of its own, so no handler version is constrained to D. --- docs/documentation/core-concepts.md | 22 ++++++++++------------ docs/specification/overview.md | 14 ++++++++------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/documentation/core-concepts.md b/docs/documentation/core-concepts.md index 4f4a9167c..1db76f13c 100644 --- a/docs/documentation/core-concepts.md +++ b/docs/documentation/core-concepts.md @@ -136,8 +136,9 @@ Each capability is identified by a reverse-domain name (e.g., are declared in the Business's UCP profile at `/.well-known/ucp`, negotiated by exact version, and confirmed in every response so that the Platform always knows the active feature set for a given interaction. UCP-authored `dev.ucp.*` -capabilities and extensions declare version `D` in each UCP release `D`. -Third-party extensions publish versions on their own cadence. +capabilities and extensions are versioned in lockstep with the specification: +each declares the date `D` of the UCP release it ships in. Third-party +extensions publish versions on their own cadence. The following are examples of capabilities defined in UCP — see the [Specification](../specification/overview.md) for the authoritative and @@ -208,10 +209,8 @@ A single service can be accessed via multiple transport bindings: A Business declares which transport bindings it supports within each service; Platforms pick whichever fits their context — an AI agent may prefer MCP, a traditional web app may use REST. Every UCP-defined service declares an explicit -`version` equal to the selected `ucp.version`; because each entry pairs the -service with one transport binding, that service `version` repeats on each entry. -Transport bindings have no separate version. Multiple UCP-defined vertical -services can coexist under one `ucp.version`, each declaring that version. +`version` equal to the selected `ucp.version`, repeated on every entry since they +differ only by transport binding. Transport bindings have no separate version. Service namespaces are also UCP's extensibility mechanism for new verticals. Businesses opt in by declaring which services they support. @@ -444,15 +443,14 @@ Businesses publish their OAuth 2.0 server metadata at UCP uses date-based version identifiers (`YYYY-MM-DD`). A UCP release `D` is a snapshot of the core protocol — its services and transport bindings, -capabilities, extensions, and shared schemas — published and certified together -as internally compatible. The selected `ucp.version` selects that snapshot; -selection is exact, so an older date is available only when the Business +capabilities, extensions, and shared schemas — published together as one +internally compatible set. A profile's `ucp.version` selects that snapshot, and +the match is exact: an older date is available only when the Business advertises it in `supported_versions`. Every UCP-defined service, capability, and extension in release `D` declares -version `D`. Capabilities and extensions are selected by exact-version -intersection. Third-party extensions and payment handlers version independently -on their authors' own schedules. +version `D`. Third-party extensions and payment handlers are versioned by their +authors, independently of UCP releases. See [Protocol Version](../specification/overview.md#protocol-version) for version discovery, [Component Versioning and Release Snapshots](../specification/overview.md#component-versioning-and-release-snapshots) diff --git a/docs/specification/overview.md b/docs/specification/overview.md index 232a0827e..df5773db6 100644 --- a/docs/specification/overview.md +++ b/docs/specification/overview.md @@ -2904,17 +2904,19 @@ UCP **MAY** backport an approved backward-compatible change — a feature, or a security, correctness, or interoperability fix — to a supported release and its generated `D` artifacts. Breaking changes enter the next release and are not backported by default; in exceptional cases, the Governance Council **MAY** -approve backporting a breaking change to a supported release, following the -breaking-change notice process. UCP **MUST** re-certify the snapshot before -publishing any updated artifacts. +approve backporting a breaking change to a supported release, limited to +defects that compromise the security, correctness, or interoperability of the +release as published, and following the breaking-change notice process. UCP +**MUST** re-certify the snapshot before publishing any updated artifacts. A Business or Platform that selects `ucp.version` `D` **MUST** declare version `D` on every `dev.ucp.*` service, capability, and extension entry in its profile. An older release is selected only through a separate `supported_versions` leaf profile whose own `ucp.version` is that older release -date (see [Protocol Version](#protocol-version)). Payment-handler versions remain -independently controlled by their authors and are not constrained -to `D`. Declaring `version` `D` does not change negotiation: `dev.ucp.*` +date (see [Protocol Version](#protocol-version)). Payment-handler versions are +controlled by their authors: UCP defines the shared declaration structure but +currently no concrete handler, so no handler version is constrained to `D`. +Declaring `version` `D` does not change negotiation: `dev.ucp.*` capabilities and extensions are still selected by exact-version intersection (see [Capability Versions](#capability-versions)). From f5581c82bb52f73fcdf6a0f6004fd2a580758653 Mon Sep 17 00:00:00 2001 From: Ilya Grigorik Date: Fri, 7 Aug 2026 07:39:23 -0700 Subject: [PATCH 6/6] cache skew, consumer validation, hosting policy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Amend the release contract per review of the backport clause: a dated release works like a long-term-support channel whose snapshot is amended in place, so document the resulting cache skew and why it is safe by construction — backports default to the backwards-compatible class, leaving earlier copies behind but never wrong. Runtime fetchers follow standard HTTP caching semantics, so refresh frequency stays publisher-controlled; build-time consumers converge on their own cadence, with exceptional breaking backports announced and enforced at request time. Unify hosting policy: HTTPS and the Cache-Control floor now cover schemas and transport descriptions alongside profiles, with validators recommended for efficient revalidation. Add the consumer-side pairing for the publisher snapshot rule: a dev.ucp.* entry whose version differs from the profile's ucp.version is rejected as not present. Add the security exception to the breaking-change notice process: the Governance Council sets disclosure timing, so fixes are not announced before they are safe to disclose. Co-authored-by: Amit Handa --- docs/documentation/core-concepts.md | 3 ++- docs/specification/overview.md | 35 +++++++++++++++++++++++------ docs/versioning.md | 3 +++ 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/docs/documentation/core-concepts.md b/docs/documentation/core-concepts.md index 1db76f13c..a8fbec62b 100644 --- a/docs/documentation/core-concepts.md +++ b/docs/documentation/core-concepts.md @@ -446,7 +446,8 @@ snapshot of the core protocol — its services and transport bindings, capabilities, extensions, and shared schemas — published together as one internally compatible set. A profile's `ucp.version` selects that snapshot, and the match is exact: an older date is available only when the Business -advertises it in `supported_versions`. +advertises it in `supported_versions`. A published snapshot can gain approved +backwards-compatible additions over time; copies fetched earlier remain valid. Every UCP-defined service, capability, and extension in release `D` declares version `D`. Third-party extensions and payment handlers are versioned by their diff --git a/docs/specification/overview.md b/docs/specification/overview.md index df5773db6..dd3e17a5c 100644 --- a/docs/specification/overview.md +++ b/docs/specification/overview.md @@ -1469,14 +1469,17 @@ and the Web Bot Auth interop signature shape, see #### Hosting -Both profiles must be reliably hosted. An unreliable or misconfigured -profile endpoint may prevent the other party from processing requests. +Profiles, and the schema and transport-description artifacts they reference, +must be reliably hosted. An unreliable or misconfigured endpoint may prevent +the other party from processing requests. -1. Profiles **MUST** be served over HTTPS. +1. Published artifacts **MUST** be served over HTTPS. 2. Profile endpoints **MUST NOT** use redirects (3xx). -3. Profile responses **MUST** include a `Cache-Control` header with - `public` and `max-age` of at least 60 seconds. Profiles **MUST NOT** - be served with `private`, `no-store`, or `no-cache` directives. +3. Published artifacts **MUST** include a `Cache-Control` header with + `public` and `max-age` of at least 60 seconds, and **MUST NOT** be + served with `private`, `no-store`, or `no-cache` directives. +4. Published artifacts **SHOULD** include a validator (`ETag` or + `Last-Modified`) so consumers can revalidate cached copies efficiently. Profiles represent a party's stable identity and capabilities. Profile URLs are expected to remain consistent across requests and not contain @@ -2909,9 +2912,27 @@ defects that compromise the security, correctness, or interoperability of the release as published, and following the breaking-change notice process. UCP **MUST** re-certify the snapshot before publishing any updated artifacts. +A dated release works like a long-term-support channel: the date names a +compatibility line, and backported changes amend the published `D` snapshot in +place — `D` stays the same while its contents change, so parties that fetched +at different times can hold different copies of the same `D`. This skew is +safe by construction: backports default to the backwards-compatible class, so +an earlier copy is missing later additions, never in conflict with them. +Parties that fetch artifacts at runtime **SHOULD** follow standard HTTP +caching semantics, revalidating a cached copy once its `max-age` expires (see +[Hosting](#hosting)); refresh frequency is therefore controlled by the +publisher. Parties that consume artifacts at build time pick up amendments on +their own release cadence. An exceptional breaking backport +is announced through the breaking-change notice process and enforced at +request time by the amended contract; it invalidates earlier copies only +because the release was defective as published. + A Business or Platform that selects `ucp.version` `D` **MUST** declare version `D` on every `dev.ucp.*` service, capability, and extension entry in its -profile. An older release is selected only through a separate +profile. A Platform that encounters a `dev.ucp.*` entry whose `version` +differs from the profile's `ucp.version` **MUST** reject that entry — treated +as not present and never activated — and **MAY** continue with the remaining +entries. An older release is selected only through a separate `supported_versions` leaf profile whose own `ucp.version` is that older release date (see [Protocol Version](#protocol-version)). Payment-handler versions are controlled by their authors: UCP defines the shared declaration structure but diff --git a/docs/versioning.md b/docs/versioning.md index 9dadc2391..14a78e5f3 100644 --- a/docs/versioning.md +++ b/docs/versioning.md @@ -38,3 +38,6 @@ re-certifies the snapshot before publishing the updated artifacts. * Breaking changes should include `!` in the PR title * Timing: We will announce the breaking change in Discussions 2 weeks before the change is merged. +* Security-sensitive fixes are the exception: the Governance Council sets + the disclosure timeline case by case, so a fix is not announced before it + is safe to disclose.