-
Notifications
You must be signed in to change notification settings - Fork 293
fix: clarify version negotiation #200
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
2155ae8
1504a88
82ce52d
f201ab7
1ebd84f
cd4d00e
356ffdf
cbe173d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,11 +33,18 @@ Schema notes: | |
|
|
||
| ## Discovery, Governance, and Negotiation | ||
|
|
||
| UCP employs a server-selects architecture where the business (server) chooses | ||
| the protocol version and capabilities from the intersection of both parties' | ||
| 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 compatibility from capability negotiation. | ||
| The business's profile at `/.well-known/ucp` describes capabilities for | ||
| its current protocol version. 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we'd also need some concept of deprecation cycle baked in. I assume businesses will not want to support every version forever, but need some way to signal to platforms "this will no longer be in |
||
| profile URI, enabling platforms to discover the exact capabilities | ||
| for a specific protocol version. 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. | ||
|
|
||
| ### Namespace Governance | ||
|
|
||
|
|
@@ -264,6 +271,35 @@ This convention ensures: | |
| - **Verifiable**: Build-time checks can confirm each `extends` entry has a | ||
| matching `$defs` key | ||
|
|
||
| ##### Protocol Version Constraint | ||
|
|
||
| Extension schemas **SHOULD** declare a `min_protocol_version` field | ||
| (alongside `name`, `title`, `description`) to indicate the minimum | ||
| UCP protocol version required by the extension: | ||
|
|
||
| ```json | ||
| { | ||
| "$schema": "https://json-schema.org/draft/2020-12/schema", | ||
| "$id": "https://acme.com/ucp/schemas/loyalty.json", | ||
| "name": "com.acme.shopping.loyalty", | ||
| "title": "Acme Loyalty Points", | ||
| "min_protocol_version": "2026-01-23", | ||
| "$defs": { ... } | ||
| } | ||
| ``` | ||
|
|
||
| The schema author — not the profile publisher — declares the minimum | ||
| protocol version requirement. The profile publisher selects and | ||
| advertises compatible versions in their profile. | ||
|
|
||
| If `min_protocol_version` is present, platforms and businesses | ||
| **SHOULD** verify the negotiated protocol version is >= | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why should, and not must here? |
||
| `min_protocol_version` during schema resolution. Incompatible | ||
| extensions are excluded from the active capability set (see | ||
| [Resolution Flow](#resolution-flow)). If absent, the extension is | ||
| assumed to be compatible with the protocol version declared by the | ||
| profile. | ||
|
|
||
| #### Schema Resolution Convention | ||
|
|
||
| To validate payloads, implementations resolve extension schemas as follows: | ||
|
|
@@ -286,8 +322,13 @@ Platforms **MUST** resolve schemas following this sequence: | |
| 2. **Negotiation**: Compute capability intersection (see | ||
| [Intersection Algorithm](#intersection-algorithm)) | ||
| 3. **Schema Fetch**: Fetch base schema and all active extension schemas | ||
| 4. **Compose**: Merge schemas via `allOf` chains based on active extensions | ||
| 5. **Validate**: Validate requests and responses against the composed schema | ||
| 4. **Protocol Compatibility**: For each fetched extension | ||
| schema, if `min_protocol_version` is present, verify the negotiated | ||
| protocol version >= that value. Exclude incompatible capabilities and | ||
| re-prune orphaned extensions (steps 3-4 of the | ||
| [Intersection Algorithm](#intersection-algorithm)) | ||
| 5. **Compose**: Merge schemas via `allOf` chains based on active extensions | ||
| 6. **Validate**: Validate requests and responses against the composed schema | ||
|
|
||
| ### Profile Structure | ||
|
|
||
|
|
@@ -402,6 +443,11 @@ used to verify signatures on webhooks and other authenticated messages from the | |
| business. See [Key Discovery](#key-discovery) for key lookup and resolution, | ||
| and [Message Signatures](signatures.md) for signing mechanics. | ||
|
|
||
| Businesses that support older protocol versions **SHOULD** include a | ||
| `supported_versions` object mapping each older version to a | ||
| version-specific profile URI. See [Protocol Version](#protocol-version) | ||
| for details. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This flow requires an additional HTTP fetch when the platform's version doesn't match version. |
||
|
|
||
| #### Platform Profile | ||
|
|
||
| Platform profiles are similar and include signing keys for capabilities | ||
|
|
@@ -574,17 +620,23 @@ for a session: | |
| 1. **Compute intersection**: For each business capability, include it in the | ||
| result if a platform capability with the same `name` exists. | ||
|
|
||
| 2. **Prune orphaned extensions**: Remove any capability where `extends` is | ||
| 2. **Select version**: For each capability in the intersection, compute the | ||
| set of version strings present in **both** the business and platform | ||
| arrays. If the set is non-empty, select the **highest** version | ||
| (latest date). If the set is empty (no mutual version), **exclude** the | ||
| capability from the intersection. | ||
|
|
||
| 3. **Prune orphaned extensions**: Remove any capability where `extends` is | ||
| set but **none** of its parent capabilities are in the intersection. | ||
| - For single-parent extensions (`extends: "string"`): parent must be present | ||
| - For multi-parent extensions (`extends: ["a", "b"]`): at least one parent | ||
| must be present | ||
|
|
||
| 3. **Repeat pruning**: Continue step 2 until no more capabilities are removed | ||
| 4. **Repeat pruning**: Continue step 3 until no more capabilities are removed | ||
| (handles transitive extension chains). | ||
|
|
||
| The result is the set of capabilities both parties support, with extension | ||
| dependencies satisfied. | ||
| The result is the set of capabilities both parties support at mutually | ||
| compatible versions, with extension dependencies satisfied. | ||
|
|
||
| #### Error Handling | ||
|
|
||
|
|
@@ -598,8 +650,8 @@ UCP negotiation can fail in two ways: | |
|
|
||
| These failure types require different handling: | ||
|
|
||
| - **Discovery failure** → transport error with optional `continue_url` | ||
| - **Negotiation failure** → UCP response with optional `continue_url` | ||
| - **Discovery or version failure** → transport error with optional `continue_url` | ||
| - **Capability negotiation failure** → UCP response with optional `continue_url` | ||
|
|
||
| ##### Error Codes | ||
|
|
||
|
|
@@ -610,8 +662,8 @@ These failure types require different handling: | |
| | `invalid_profile_url` | Profile URL is malformed, missing, or unresolvable | 400 | -32001 | | ||
| | `profile_unreachable` | Resolved URL but fetch failed (timeout, non-2xx) | 424 | -32001 | | ||
| | `profile_malformed` | Fetched content is not valid JSON or violates schema | 422 | -32001 | | ||
| | `version_unsupported` | Platform's protocol version not supported | 422 | -32001 | | ||
| | `capabilities_incompatible` | No compatible capabilities in intersection | 200 | result | | ||
| | `version_unsupported` | Platform's UCP version is not supported | 200 | result | | ||
|
|
||
| **Signature Errors:** | ||
|
|
||
|
|
@@ -670,7 +722,20 @@ task through the standard web interface. | |
| } | ||
| ``` | ||
|
|
||
| **Negotiation Failure (200):** | ||
| **Version Unsupported (422):** | ||
|
|
||
| ```http | ||
| HTTP/1.1 422 Unprocessable Content | ||
| Content-Type: application/json | ||
|
|
||
| { | ||
| "code": "version_unsupported", | ||
| "content": "Protocol version 2026-01-12 is not supported. This business supports versions 2026-01-11 and 2026-01-23.", | ||
| "continue_url": "https://merchant.com/cart" | ||
| } | ||
| ``` | ||
|
|
||
| **Capabilities Incompatible (200):** | ||
|
|
||
| ```http | ||
| HTTP/1.1 200 OK | ||
|
|
@@ -684,9 +749,9 @@ task through the standard web interface. | |
| "messages": [ | ||
| { | ||
| "type": "error", | ||
| "code": "version_unsupported", | ||
| "content": "UCP version 2024-01-01 is not supported", | ||
| "severity": "requires_buyer_input" | ||
| "code": "capabilities_incompatible", | ||
| "content": "No compatible capabilities found", | ||
| "severity": "recoverable" | ||
| } | ||
| ], | ||
| "continue_url": "https://merchant.com" | ||
|
|
@@ -730,7 +795,25 @@ task through the standard web interface. | |
| } | ||
| ``` | ||
|
|
||
| **Negotiation Failure (JSON-RPC result):** | ||
| **Version Unsupported (JSON-RPC error):** | ||
|
|
||
| ```json | ||
| { | ||
| "jsonrpc": "2.0", | ||
| "id": 1, | ||
| "error": { | ||
| "code": -32001, | ||
| "message": "Protocol version not supported", | ||
| "data": { | ||
| "code": "version_unsupported", | ||
| "content": "Protocol version 2026-01-12 is not supported. This business supports versions 2026-01-11 and 2026-01-23.", | ||
| "continue_url": "https://merchant.com/cart" | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| **Capabilities Incompatible (JSON-RPC result):** | ||
|
|
||
| ```json | ||
| { | ||
|
|
@@ -745,9 +828,9 @@ task through the standard web interface. | |
| "messages": [ | ||
| { | ||
| "type": "error", | ||
| "code": "version_unsupported", | ||
| "content": "UCP version 2024-01-01 is not supported", | ||
| "severity": "requires_buyer_input" | ||
| "code": "capabilities_incompatible", | ||
| "content": "No compatible capabilities found", | ||
| "severity": "recoverable" | ||
| } | ||
| ], | ||
| "continue_url": "https://merchant.com" | ||
|
|
@@ -1642,16 +1725,74 @@ Both businesses and platforms declare a single version in their profiles: | |
|
|
||
|  | ||
|
|
||
| Businesses **MUST** validate the platform's version and determine compatibility: | ||
| 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 | ||
|
|
||
| The `version` field declares the business's current protocol version. | ||
| The profile at `/.well-known/ucp` describes the capabilities, services, | ||
| 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. | ||
|
|
||
| ```json | ||
| { | ||
| "ucp": { | ||
| "version": "2026-01-23", | ||
| "supported_versions": { | ||
| "2026-01-11": "https://business.example.com/.well-known/ucp/2026-01-11" | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ##### Initial Service and Capability Discovery | ||
|
|
||
| Platforms discover a business's capabilities through the following flow: | ||
|
|
||
| 1. 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 | ||
| 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` | ||
| field. | ||
|
|
||
| 1. Platform declares version via profile referenced in request | ||
| ##### Request-Time Validation | ||
|
|
||
| Businesses **MUST** validate the platform's protocol version on | ||
| every request: | ||
|
|
||
| 1. Platform declares the protocol version it uses via the | ||
| `version` field in the profile referenced in the request. | ||
| 2. Business validates: | ||
| - If platform version ≤ business version: Business **MUST** | ||
| process the request | ||
| - If platform version > business version: Business **MUST** return | ||
| `version_unsupported` error | ||
| 3. Businesses **MUST** include the version used for processing in every | ||
| response. | ||
| - If the platform's `version` matches the business's `version` | ||
| or is a key in `supported_versions`: the request **MAY** | ||
| proceed to capability negotiation using the matching | ||
| version of the business profile. | ||
| - Otherwise: Business **MUST** return a `version_unsupported` | ||
| error. | ||
| 3. Businesses **MUST** include the negotiated protocol version in | ||
| every response. | ||
|
|
||
| Response with version confirmation: | ||
|
|
||
|
|
@@ -1668,20 +1809,34 @@ Response with version confirmation: | |
| } | ||
| ``` | ||
|
|
||
| Version unsupported error: | ||
| Version unsupported error (protocol-level, returned before capability | ||
| negotiation): | ||
|
|
||
| ```http | ||
| HTTP/1.1 422 Unprocessable Content | ||
| Content-Type: application/json | ||
|
|
||
| ```json | ||
| { | ||
| "status": "requires_escalation", | ||
| "messages": [{ | ||
| "type": "error", | ||
| "code": "version_unsupported", | ||
| "content": "Version 2026-01-12 is not supported. This business implements version 2026-01-11.", | ||
| "severity": "requires_buyer_input" | ||
| }] | ||
| "code": "version_unsupported", | ||
| "content": "Protocol version 2026-01-12 is not supported. This business supports versions 2026-01-11 and 2026-01-23.", | ||
| "continue_url": "https://merchant.com/cart" | ||
| } | ||
| ``` | ||
|
|
||
| #### Capability Versions | ||
|
|
||
| Capability versions are negotiated independently of the protocol | ||
igrigorik marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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. | ||
|
|
||
| Businesses **MUST** include only capabilities compatible with the | ||
igrigorik marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| negotiated protocol version in their response. A capability that | ||
| depends on features introduced in a newer protocol version **MUST | ||
| NOT** be included when processing at an older protocol version. | ||
|
|
||
| ### Backwards Compatibility | ||
|
|
||
| #### Backwards-Compatible Changes | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is "current" synonymous with "latest"/ "most recent" here?