Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 194 additions & 39 deletions docs/specification/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

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?

versions **SHOULD** publish version-specific profiles and advertise them
via the `supported_versions` field — a map from protocol version to
Copy link
Contributor

Choose a reason for hiding this comment

The 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 supported_versions on this date"

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

Expand Down Expand Up @@ -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 >=
Copy link
Contributor

Choose a reason for hiding this comment

The 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:
Expand All @@ -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

Expand Down Expand Up @@ -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.

Choose a reason for hiding this comment

The 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
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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:**

Expand Down Expand Up @@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -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
{
Expand All @@ -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"
Expand Down Expand Up @@ -1642,16 +1725,74 @@ Both businesses and platforms declare a single version in their profiles:

![High-level resolution flow sequence diagram](site:specification/images/ucp-discovery-negotiation.png)

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:

Expand All @@ -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
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
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
Expand Down
14 changes: 13 additions & 1 deletion source/schemas/ucp.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@
"type": "object",
"required": ["version"],
"properties": {
"version": { "$ref": "#/$defs/version" },
"version": {
"$ref": "#/$defs/version",
"description": "Protocol version this profile describes. The profile's capabilities, services, and payment handlers are defined for this version."
},
"services": {
"type": "object",
"description": "Service registry keyed by reverse-domain name.",
Expand Down Expand Up @@ -122,6 +125,15 @@
{
"required": ["services", "payment_handlers"],
"properties": {
"supported_versions": {
"type": "object",
"description": "Previous protocol versions this business supports, mapped to profile URIs. Businesses that support older protocol versions SHOULD advertise each version and link to its profile. Each URI points to a complete, self-contained profile for that version. When omitted, only `version` is supported.",
"propertyNames": { "$ref": "#/$defs/version" },
"additionalProperties": {
"type": "string",
"format": "uri"
}
},
"services": {
"additionalProperties": {
"items": { "$ref": "service.json#/$defs/business_schema" }
Expand Down
Loading