fix!: repair profile schemas to pass validation & endpoint contract#429
Conversation
The platform_schema in service.json required `endpoint` for rest/mcp/a2a
transports. This was structurally wrong: the protocol has no consumer that
reads service-level endpoint on the platform side.
- Negotiation algorithm (overview.md §"Capability Negotiation") reads
version + extends from the platform profile, not endpoint.
- Key discovery (overview.md §"Key Discovery") is verification-from-headers
via UCP-Agent profile URL, not callable RPC against the platform.
- Webhook delivery (business → platform) is modeled at the capability
layer via `config.webhook_url` (see order.json#/platform_schema and
order.md §"Order Event Webhook"), not at the service layer.
The spec's own Platform Profile example (overview.md §"Platform Profile")
omits endpoint and consequently fails to validate against this branch of
its own schema. With this fix the canonical example validates.
The top-level discriminator in profile_schema.json was `oneOf`, which
requires data to match exactly one branch. The two branches (platform_profile,
business_profile) have no required-vs-forbidden split and no type marker,
so any document fully describing itself (e.g., a business profile that
includes spec + schema URLs alongside endpoint) satisfies both branches
and gets rejected with "must match exactly one schema in oneOf."
Reproduction with ajv + JSON Schema 2020-12:
Fixture | oneOf | anyOf
---------------------------------------|--------|------
Platform agent (spec+schema) | PASS | PASS
Hosted-platform (spec+schema+endpoint) | FAIL | PASS
Business (endpoint only) | PASS | PASS
Well-described business (all fields) | FAIL | PASS
The second and fourth fixtures are the bug — both represent legitimate
profiles that fail validation.
Switching to `anyOf` matches what the data model actually expresses
("a profile is platform-shaped, business-shaped, or both"). Consumers
that want self-describing parse can $ref the specific branch they expect
based on where they dereferenced (UCP-Agent header → platform_profile;
/.well-known/ucp → business_profile).
The shopping MCP openrpc doc declared a `signature` field in the request meta object referencing RFC 7515 detached JWS. This contradicts the Message Signatures specification (docs/specification/signatures.md), which is unambiguous: UCP wire signing uses RFC 9421 (HTTP Message Signatures) + RFC 9530 (Content-Digest), carried in HTTP headers, not in the payload. The field was a leftover from an earlier signing-scheme exploration. RFC 7515 detached JWS still has one legitimate use in UCP — AP2 mandate signing — but that's payload-level by design and unrelated to request signing. It does not belong on MCP openrpc meta.
There was a problem hiding this comment.
| "description": "Full service declaration for platform-level discovery. All | |
| transports require `version`, `spec`, and `transport`. REST, MCP, and embedded | |
| additionally require `schema`." |
|
Thanks for catching this @igrigorik. I was wondering if there is a way for us to catch All three bugs in this PR lived in the same uncovered space: schema logic for The result: Suggestion: close the discovery/profile validation blind spot in
|
|
Thanks Ilya. @amithanda, agree we need CI to catch this. I opened a new issue in the .github repo to track: Universal-Commerce-Protocol/.github#21 |
Two prose surfaces still described the pre-fix endpoint contract:
- source/schemas/service.json: platform_schema.description listed
`endpoint` as required on REST/MCP/A2A — contradicting the schema
after the endpoint-requirement fix earlier in this PR.
- docs/documentation/schema-authoring.md: the Service Schemas section
listed transport requirements flat (no platform-vs-business split)
with `endpoint` required on REST/MCP/A2A across the board.
Both now reflect the actual contract: platform profiles require
`schema` on REST/MCP/Embedded with A2A base-only; business profiles
require `endpoint` on REST/MCP/A2A with Embedded base-only.
|
@amithanda good catch on the prose -- ty! Pushed a fix. I agree that we need to validate and enforce this in CI. Worth nothing that all of the above bugs have associated examples inlined in our spec text, but today we do not validate them — the examples were shaped right, the schemas did not match the intent. As a first pass, I'd suggest we finish and land the pattern we're exploring in #359. I think above would have caught these bugs. We can certainly explore adding and validating |
@igrigorik - Yes, Agree that validating and enforcing via CI checks can come in a follow up PR and we should move ahead with the changes in this PR. I am also open to back porting these to previous versions given this is more of a bug. |
* fix: add missing ucp_agent parameter to create_cart REST operation (#362) create_cart is the only REST operation (out of 14) missing the required UCP-Agent header parameter. All other operations — including get_cart, update_cart, and cancel_cart — include it. The spec states "All requests MUST include the UCP-Agent header." * fix!: repair profile schemas to pass validation & endpoint contract (#429) * fix(service): drop endpoint requirement from platform schema The platform_schema in service.json required `endpoint` for rest/mcp/a2a transports. This was structurally wrong: the protocol has no consumer that reads service-level endpoint on the platform side. - Negotiation algorithm (overview.md §"Capability Negotiation") reads version + extends from the platform profile, not endpoint. - Key discovery (overview.md §"Key Discovery") is verification-from-headers via UCP-Agent profile URL, not callable RPC against the platform. - Webhook delivery (business → platform) is modeled at the capability layer via `config.webhook_url` (see order.json#/platform_schema and order.md §"Order Event Webhook"), not at the service layer. The spec's own Platform Profile example (overview.md §"Platform Profile") omits endpoint and consequently fails to validate against this branch of its own schema. With this fix the canonical example validates. * fix(discovery): replace top-level oneOf with anyOf in profile schema The top-level discriminator in profile_schema.json was `oneOf`, which requires data to match exactly one branch. The two branches (platform_profile, business_profile) have no required-vs-forbidden split and no type marker, so any document fully describing itself (e.g., a business profile that includes spec + schema URLs alongside endpoint) satisfies both branches and gets rejected with "must match exactly one schema in oneOf." Reproduction with ajv + JSON Schema 2020-12: Fixture | oneOf | anyOf ---------------------------------------|--------|------ Platform agent (spec+schema) | PASS | PASS Hosted-platform (spec+schema+endpoint) | FAIL | PASS Business (endpoint only) | PASS | PASS Well-described business (all fields) | FAIL | PASS The second and fourth fixtures are the bug — both represent legitimate profiles that fail validation. Switching to `anyOf` matches what the data model actually expresses ("a profile is platform-shaped, business-shaped, or both"). Consumers that want self-describing parse can $ref the specific branch they expect based on where they dereferenced (UCP-Agent header → platform_profile; /.well-known/ucp → business_profile). * fix(mcp): drop stale signature field from openrpc meta The shopping MCP openrpc doc declared a `signature` field in the request meta object referencing RFC 7515 detached JWS. This contradicts the Message Signatures specification (docs/specification/signatures.md), which is unambiguous: UCP wire signing uses RFC 9421 (HTTP Message Signatures) + RFC 9530 (Content-Digest), carried in HTTP headers, not in the payload. The field was a leftover from an earlier signing-scheme exploration. RFC 7515 detached JWS still has one legitimate use in UCP — AP2 mandate signing — but that's payload-level by design and unrelated to request signing. It does not belong on MCP openrpc meta. * docs(schemas): update prose to match transport contract Two prose surfaces still described the pre-fix endpoint contract: - source/schemas/service.json: platform_schema.description listed `endpoint` as required on REST/MCP/A2A — contradicting the schema after the endpoint-requirement fix earlier in this PR. - docs/documentation/schema-authoring.md: the Service Schemas section listed transport requirements flat (no platform-vs-business split) with `endpoint` required on REST/MCP/A2A across the board. Both now reflect the actual contract: platform profiles require `schema` on REST/MCP/Embedded with A2A base-only; business profiles require `endpoint` on REST/MCP/A2A with Embedded base-only. --------- Co-authored-by: Patrick R. Jordan <patrick.r.jordan@gmail.com> Co-authored-by: Ilya Grigorik <ilya@grigorik.com>
…rsal-Commerce-Protocol#440) * fix: add missing ucp_agent parameter to create_cart REST operation (Universal-Commerce-Protocol#362) create_cart is the only REST operation (out of 14) missing the required UCP-Agent header parameter. All other operations — including get_cart, update_cart, and cancel_cart — include it. The spec states "All requests MUST include the UCP-Agent header." * fix!: repair profile schemas to pass validation & endpoint contract (Universal-Commerce-Protocol#429) * fix(service): drop endpoint requirement from platform schema The platform_schema in service.json required `endpoint` for rest/mcp/a2a transports. This was structurally wrong: the protocol has no consumer that reads service-level endpoint on the platform side. - Negotiation algorithm (overview.md §"Capability Negotiation") reads version + extends from the platform profile, not endpoint. - Key discovery (overview.md §"Key Discovery") is verification-from-headers via UCP-Agent profile URL, not callable RPC against the platform. - Webhook delivery (business → platform) is modeled at the capability layer via `config.webhook_url` (see order.json#/platform_schema and order.md §"Order Event Webhook"), not at the service layer. The spec's own Platform Profile example (overview.md §"Platform Profile") omits endpoint and consequently fails to validate against this branch of its own schema. With this fix the canonical example validates. * fix(discovery): replace top-level oneOf with anyOf in profile schema The top-level discriminator in profile_schema.json was `oneOf`, which requires data to match exactly one branch. The two branches (platform_profile, business_profile) have no required-vs-forbidden split and no type marker, so any document fully describing itself (e.g., a business profile that includes spec + schema URLs alongside endpoint) satisfies both branches and gets rejected with "must match exactly one schema in oneOf." Reproduction with ajv + JSON Schema 2020-12: Fixture | oneOf | anyOf ---------------------------------------|--------|------ Platform agent (spec+schema) | PASS | PASS Hosted-platform (spec+schema+endpoint) | FAIL | PASS Business (endpoint only) | PASS | PASS Well-described business (all fields) | FAIL | PASS The second and fourth fixtures are the bug — both represent legitimate profiles that fail validation. Switching to `anyOf` matches what the data model actually expresses ("a profile is platform-shaped, business-shaped, or both"). Consumers that want self-describing parse can $ref the specific branch they expect based on where they dereferenced (UCP-Agent header → platform_profile; /.well-known/ucp → business_profile). * fix(mcp): drop stale signature field from openrpc meta The shopping MCP openrpc doc declared a `signature` field in the request meta object referencing RFC 7515 detached JWS. This contradicts the Message Signatures specification (docs/specification/signatures.md), which is unambiguous: UCP wire signing uses RFC 9421 (HTTP Message Signatures) + RFC 9530 (Content-Digest), carried in HTTP headers, not in the payload. The field was a leftover from an earlier signing-scheme exploration. RFC 7515 detached JWS still has one legitimate use in UCP — AP2 mandate signing — but that's payload-level by design and unrelated to request signing. It does not belong on MCP openrpc meta. * docs(schemas): update prose to match transport contract Two prose surfaces still described the pre-fix endpoint contract: - source/schemas/service.json: platform_schema.description listed `endpoint` as required on REST/MCP/A2A — contradicting the schema after the endpoint-requirement fix earlier in this PR. - docs/documentation/schema-authoring.md: the Service Schemas section listed transport requirements flat (no platform-vs-business split) with `endpoint` required on REST/MCP/A2A across the board. Both now reflect the actual contract: platform profiles require `schema` on REST/MCP/Embedded with A2A base-only; business profiles require `endpoint` on REST/MCP/A2A with Embedded base-only. --------- Co-authored-by: Patrick R. Jordan <patrick.r.jordan@gmail.com> Co-authored-by: Ilya Grigorik <ilya@grigorik.com>
…changes into 2026-04-08 version (#433) * update line item ids to differentiate from item ids (#112) * fix: standardize package registries in lockfile (#368) Regenerated the lockfile to ensure all dependencies are resolved from the default public registries configured in pyproject.toml. This removes environment-specific registry URLs that were inadvertently included. * Update signature requirements in documentation (format only) (#242) Split the notes in different lines to fix rendering Co-authored-by: Guillaume V. <4216770+ptiper@users.noreply.github.com> * docs: fix inconsistencies in specification examples (#363) * fix: add missing ucp_agent parameter to create_cart REST operation create_cart is the only REST operation (out of 14) missing the required UCP-Agent header parameter. All other operations — including get_cart, update_cart, and cancel_cart — include it. The spec states "All requests MUST include the UCP-Agent header." * docs: fix inconsistencies in specification examples Fix five documentation bugs where examples diverged from schema definitions: - Fix CheckoutCom.svg fallback text showing "Chewy" instead of "Checkout.com" (docs/index.md, 2 occurrences) - Fix invalid checkout status "ready_for_payment" → "ready_for_complete" (buyer-consent.md) - Fix wrong PostalAddress field "address_street" → "street_address" (embedded-checkout.md) - Fix singular/plural JSONPath "method[0]" → "methods[0]" (checkout-rest.md) - Replace deprecated "risk_signal" with "signals" using reverse-domain keys (processor-tokenizer-payment-handler.md) * docs: fix accuracy issues in documentation pages (#365) - Replace hardcoded version "2026-01-11" with {{ ucp_version }} template variable in playground.md (3 UCP version fields; third-party URLs left unchanged) - Add EP (Embedded Protocol) to transport examples list in core-concepts.md, matching the four transports defined in the spec - Update roadmap to reflect that product discovery, cart, and post-order management are now part of the specification * docs: remove entity wrapper from MCP response examples (#360) MCP response examples incorrectly nested the UCP payload under a "checkout", "cart", or "order" key inside structuredContent. The OpenRPC result.name field is descriptive metadata — it does not create a wrapper key in the JSON-RPC wire format (per the OpenRPC spec, name only affects params in by-name mode, not results). This aligns success response examples with error responses, which already placed the UCP envelope directly in structuredContent without a wrapper. Addresses the same issue as the closed #239 — that fix was deferred to #216, which corrected error responses but left success responses wrapped. * fix: render Total and Totals fields on schema reference page (#352) The Total and Totals sections on the specification reference page rendered empty tables because `_render_table_from_schema` entered the `allOf` branch for schemas that have both `properties` and `allOf` validation constraints (if/then, contains). The allOf items contained no renderable content, so no table rows were emitted. Two fixes in `_render_table_from_schema`: 1. Only enter the `allOf` rendering branch when `properties` is empty (`and not properties`). This lets Total's three fields (type, display_text, amount) render through the normal property iteration path. 2. Treat array-typed schemas (like Totals) the same as scalar types in the description fallback, since their top-level `allOf` only carries `contains` constraints, not composition. Totals now renders its description text, consistent with Amount and Signed Amount. Made-with: Cursor * docs: Correct profile examples in docs Playground widget (#236) * Correct payment handler profile example in playground * Remove trailing space from playground.md --------- Co-authored-by: Guillaume V. <4216770+ptiper@users.noreply.github.com> * docs: Modernize text diagram in signatures.md (#331) * fix: Fix inconsistent schema on documentation examples & discounts extension schema (#371) * Documentation fix to discounts extension to make sure it is compliant with the spec. * Clean up unsupported field in checkout examples for quantity-related errors. * Fix discount schema to use UCP specific annotation instead of readOnly. * docs: add extensibility and forward compatibility guidelines (#290) - Added "Extensibility and Forward Compatibility" section to the schema authoring guide. - Defined standards for Open vs. Closed Enumerations to prevent breaking changes in code-generated clients. - Added guidance on avoiding 'additionalProperties: false' to ensure forward compatibility for objects. - Clarified the impact of modern code generators (e.g., Quicktype) on schema-to-type validation. - Updated the comprehensive capability schema example to align with these new extensibility rules. Co-authored-by: Guillaume V. <4216770+ptiper@users.noreply.github.com> * docs: add centralized glossary and acronym standards (#241) - Organize terms by category (Commerce, Payments, etc.). - Define financial acronyms previously used but not expanded. - Establish guidelines for first-use acronyms in Markdown files. Ref: #214 (comment) Co-authored-by: Ilya Grigorik <ilya@grigorik.com> * docs: enrich core-concepts with comprehensive UCP protocol overview (#336) * feat: attribution field for platform referral context (#391) * feat: attribution field for platform referral context Adds top-level `attribution` carrying platform-emitted referral and conversion-event context (campaigns, click IDs, source/medium markers) — the agentic counterpart of URL query parameters in browser-based flows. - Core field, not an extension. Attribution is informational; its presence or absence does not affect protocol behavior, so capability negotiation earns nothing. No registry entry, no extension declaration. - Open string-keyed map. UCP does not prescribe attribution models, vocabularies, or touchpoint logic. Platforms use their existing conventions (GA4 campaign parameters, click identifiers like gclid / fbclid / ttclid). - Lives on cart, checkout, and catalog requests as platform-provided input; appears on order as a business-emitted snapshot of the originating checkout's attribution. * drop direct-identifier clause What counts as "identifying" depends on jurisdiction and on the agent's and business's data context. Any enumeration is also incomplete. UCP can't usefully encode this at the schema level. The privacy paragraph already carries the compliance posture, the schema description positively scopes the field, and `buyer` is the canonical home for direct identifiers. * docs: document min/max property-count default By default, UCP schemas do not set `minProperties` or `maxProperties` on object fields. maxProperties caps are deferred to implementers — the protocol does not define them because any specific limit requires judgment calls that inevitably hit exceptions; implementers should impose their own constraints with clear error feedback. minProperties is omitted because empty objects are well-formed and harmless; implementers process them as a no-op. * docs: Fix typos and improve formatting in index and versioning (#416) * fix(docs): omit deprecated checkout id from playground payload (#332) Removes checkoutResponse.id from the update payload in playground.md. This field is marked as deprecated_required_to_omit for update requests (as the ID is now passed in the URL path). It was previously only commented as deprecated without actually removing the property from the payload object. * docs: adds descriptions to links in llms-txt (#419) * fix!: Cherrypick critical schema fixes into 2026-04-08 version (#440) * fix: add missing ucp_agent parameter to create_cart REST operation (#362) create_cart is the only REST operation (out of 14) missing the required UCP-Agent header parameter. All other operations — including get_cart, update_cart, and cancel_cart — include it. The spec states "All requests MUST include the UCP-Agent header." * fix!: repair profile schemas to pass validation & endpoint contract (#429) * fix(service): drop endpoint requirement from platform schema The platform_schema in service.json required `endpoint` for rest/mcp/a2a transports. This was structurally wrong: the protocol has no consumer that reads service-level endpoint on the platform side. - Negotiation algorithm (overview.md §"Capability Negotiation") reads version + extends from the platform profile, not endpoint. - Key discovery (overview.md §"Key Discovery") is verification-from-headers via UCP-Agent profile URL, not callable RPC against the platform. - Webhook delivery (business → platform) is modeled at the capability layer via `config.webhook_url` (see order.json#/platform_schema and order.md §"Order Event Webhook"), not at the service layer. The spec's own Platform Profile example (overview.md §"Platform Profile") omits endpoint and consequently fails to validate against this branch of its own schema. With this fix the canonical example validates. * fix(discovery): replace top-level oneOf with anyOf in profile schema The top-level discriminator in profile_schema.json was `oneOf`, which requires data to match exactly one branch. The two branches (platform_profile, business_profile) have no required-vs-forbidden split and no type marker, so any document fully describing itself (e.g., a business profile that includes spec + schema URLs alongside endpoint) satisfies both branches and gets rejected with "must match exactly one schema in oneOf." Reproduction with ajv + JSON Schema 2020-12: Fixture | oneOf | anyOf ---------------------------------------|--------|------ Platform agent (spec+schema) | PASS | PASS Hosted-platform (spec+schema+endpoint) | FAIL | PASS Business (endpoint only) | PASS | PASS Well-described business (all fields) | FAIL | PASS The second and fourth fixtures are the bug — both represent legitimate profiles that fail validation. Switching to `anyOf` matches what the data model actually expresses ("a profile is platform-shaped, business-shaped, or both"). Consumers that want self-describing parse can $ref the specific branch they expect based on where they dereferenced (UCP-Agent header → platform_profile; /.well-known/ucp → business_profile). * fix(mcp): drop stale signature field from openrpc meta The shopping MCP openrpc doc declared a `signature` field in the request meta object referencing RFC 7515 detached JWS. This contradicts the Message Signatures specification (docs/specification/signatures.md), which is unambiguous: UCP wire signing uses RFC 9421 (HTTP Message Signatures) + RFC 9530 (Content-Digest), carried in HTTP headers, not in the payload. The field was a leftover from an earlier signing-scheme exploration. RFC 7515 detached JWS still has one legitimate use in UCP — AP2 mandate signing — but that's payload-level by design and unrelated to request signing. It does not belong on MCP openrpc meta. * docs(schemas): update prose to match transport contract Two prose surfaces still described the pre-fix endpoint contract: - source/schemas/service.json: platform_schema.description listed `endpoint` as required on REST/MCP/A2A — contradicting the schema after the endpoint-requirement fix earlier in this PR. - docs/documentation/schema-authoring.md: the Service Schemas section listed transport requirements flat (no platform-vs-business split) with `endpoint` required on REST/MCP/A2A across the board. Both now reflect the actual contract: platform profiles require `schema` on REST/MCP/Embedded with A2A base-only; business profiles require `endpoint` on REST/MCP/A2A with Embedded base-only. --------- Co-authored-by: Patrick R. Jordan <patrick.r.jordan@gmail.com> Co-authored-by: Ilya Grigorik <ilya@grigorik.com> * feat!: identity linking OAuth 2.0 foundation with capability-driven scopes (#354) (#431) * refactor: Update terminology and clarify roles in UCP documentation - Replace "consumer surfaces/platforms" with "consumer platforms" and "businesses" with "business platforms" for consistency. - Enhance the definitions of consumer and business platforms, emphasizing their roles in capability consumption and exposure. - Revise key goals and responsibilities to reflect updated terminology and clarify the interaction dynamics within the UCP framework. - Introduce a new section on capabilities, detailing their structure and examples to improve understanding of UCP's functionality. * docs: update core concepts and capabilities in UCP documentation - Clarified the role of Payment & Credential Providers to emphasize the secure handling of sensitive user data. - Enhanced the description of Agentic Commerce to include various modalities for AI agents. - Revised terminology for distinct actors in the UCP framework to improve clarity. - Updated capability negotiation process to specify version selection and mutual agreement. - Improved examples and descriptions for capabilities and transport bindings to align with current standards. * docs: refine terminology and clarify roles in UCP documentation - Updated terminology to replace "consumer platforms" with "clients" and "business platforms" with "providers" for consistency and clarity. - Enhanced descriptions of the roles and responsibilities of clients and providers in the UCP framework. - Revised key goals and capabilities to reflect the updated terminology and improve understanding of UCP's functionality. * docs: improve formatting and clarity in UCP core concepts documentation * docs: minor fix to doc and line wrap * docs: update terminology and clarify roles in UCP documentation - Replaced "Client" with "Platform" and "Provider" with "Business" for consistency. * docs: enhance identity linking capability in UCP documentation - Updated the identity linking specification to clarify the role of platforms and businesses in buyer-authenticated commerce experiences. - Introduced a new JSON schema for identity linking, detailing the configuration for capabilities that require buyer identity. - Revised the overview and general guidelines sections to reflect the updated terminology and structure for identity linking capabilities. - Added new error code for identity requirements in the shopping types schema. * chore: revert core-concepts.md to upstream version Restores docs/documentation/core-concepts.md to match Universal-Commerce-Protocol/ucp upstream main. The local changes belong to a separate PR and should not be included here. * docs: update identity linking terminology in specifications - Renamed the `required` field to `auth_required` in the identity linking specification and JSON schema to enhance clarity regarding buyer identity requirements. * fix: schema convention, field naming, iss validation Four targeted fixes to prepare this PR for backport to 04/08 and clean stacking of the delegated IdP follow-up. ## 1. Nest $defs under capability name (convention alignment) Restructures the schema to match the established pattern required by the composition algorithm: `ext_schema["$defs"][root.name]` Before: $defs: After: $defs: capability_identity_config dev.ucp.common.identity_linking: platform_schema, business_schema Why: capability-scoped schemas live under the capability's reverse-domain name so future tooling can resolve them predictably as `schema#/$defs/{capability-name}/business_schema`. ## 2. Fix `required` → `auth_required` in overview.md The business profile example in `overview.md` used `"required": true` while the schema and spec text use `"auth_required"`. Anyone copying the overview example would hit a validation error. ## 3. Remove top-level `version` field from schema No other capability schema in the repo carries a top-level `version` field — version lives on the capability entry in the UCP profile, not on the schema file itself. Removed for consistency with `checkout.json`, `fulfillment.json`, `cart.json`, etc. ## 4. Tighten `iss` validation language Removed the "if present" hedge in two places (For Platforms bullet and Account Linking Flow step 3). Since the spec requires businesses to MUST return `iss` in every authorization response, the hedge was unnecessary and could be read as making `iss` validation conditional. ## 5. $comment updated to reflect unified providers model The schema-level `$comment` previously described `providers` and `mechanisms` as two separate reserved extension points. Updated to describe a single `providers` map with a `type` discriminator defaulting to `oauth2` — aligning with feedback on #354 that these are the same concept (a trust-anchored identity source with a discovery mechanism and proof protocol), not separate keys. This is $comment-only — no schema behavior change — and gives the follow-up IdP PR a clean model to add `providers` onto without rewriting the Future Extensibility section. * docs:Updated the identity linking specification to clarify the support for both delegated identity providers and non-OAuth authentication mechanisms through a unified `config.providers` extension point. * docs: Update identity linking terminology from "buyer" to "user" across multiple files and update RFC links - Changed references from "buyer" to "user" in identity-linking documentation to reflect updated terminology. - Updated the description in the identity linking schema to specify user-scoped features instead of buyer-scoped. - Adjusted related documentation sections to ensure consistency in terminology and clarify user authentication requirements. * refactor: flatten scopes to wire-format-keyed map Restructures identity linking scope configuration based on TC discussion. `config.capabilities` → `config.scopes`. Flat map keyed by wire-format scope token (`{capability}:{scope}`); keys are exactly what platforms put into OAuth `scope=` parameters. ```json "config": { "scopes": { "dev.ucp.shopping.order:read": {}, "dev.ucp.shopping.order:manage": {} } } - auth_required removed. Scope presence in the map is the signal: listed → user auth required; not listed → public/agent-auth access. - Each value is an open scope_policy object for per-scope auth constraints (e.g. min_acr, max_token_age) and future metadata. Platforms MUST ignore unrecognized fields. - New scope_token $def with regex enforcing {reverse-domain}:{name} format; removes ambiguity around bare-capability scopes. * Add scopes section for checkout capability * docs: Enhance identity linking specification with new error handling and token validation requirements - Added requirements for handling `insufficient_scope` errors, including the need for businesses to return specific messages identifying missing scopes. - Updated the `identity_required` section to clarify conditions under which it should be triggered. - Introduced the `UCP-Identity-Token` header for user identity tokens when using platform credentials. * docs: Update identity linking specification to include loopback redirect handling * docs: Add scopes sections for cart, order, and catalog capabilities * docs: clarify OAuth 2.0 usage and drop UCP-Identity-Token * support public clients via PKCE The MUST on client_secret_basic excluded native, desktop, and on-device agent runtimes (RFC 8252 §8.5 — public clients cannot keep a client_secret) and also blocked stronger asymmetric methods (private_key_jwt RFC 7523, tls_client_auth RFC 8705). The IdP support we want to land next requires asymmetric crypto for JWT bearer assertions. Replaces the single-method MUST with RFC-8414-driven negotiation: - Confidential clients SHOULD prefer asymmetric methods; MAY use client_secret_basic. - Public clients (RFC 8252 §8.5) MUST use 'none' and rely on PKCE with S256 as proof-of-possession; MUST NOT embed a client_secret. - Businesses declare methods in token_endpoint_auth_methods_supported; SHOULD support an asymmetric method; MAY support 'none' for public clients. PKCE S256 required when 'none' is advertised. - Distinct error codes: invalid_client for auth-method failures, invalid_grant for PKCE failures. * docs: normalize capability scope sections Per TC discussion default access is a merchant policy decision, not a spec mandate. UCP defines well-known scopes; merchants decide what auth is required for non-scoped operations. - Remove default-access framing from capability specs. Each section now states only the well-known scopes the capability defines. - Tighten scope descriptions to a consistent shape: "<operations gated> — <data or behavior unlocked>". - Hoist protocol-level rules (declaration, derivation, well-known vs custom extension) into identity-linking.md, where they live once. Capability specs link to that section instead of duplicating. - Switch the identity-linking.md B2B walkthrough from dev.ucp.shopping.checkout:create to :manage. The well-known scope fits the "no guest checkout" narrative more cleanly (gates all checkout ops, not just the entry point). * Add an optional description field to the scope_policy object in the identity_linking.json schema. * document identity_optional and relation to scope description * `scope_policy.description`: $ref shared `description.json` type for multi-format text (plain/markdown/html). Cross-domain ref to shopping/types is the minimal change; promoting the type to common/types is a follow-up. * New `## Optional Authentication` section + `identity_optional` info-severity code. Decoupled from per-scope `description` by design: identity_optional is a runtime per-request notice; description is static per-scope context for OAuth consent. * `insufficient_scope` example fixed: response lists the FULL required scope set, not the delta (per Amit's TC restatement). Platform computes the diff and uses incremental authorization to avoid redundant consent prompts. continue_url wording aligned. * Schema descriptions: drop "public or agent-authenticated access" framing. TC consensus is that UCP does not prescribe a default; merchants decide access policy for non-scoped operations. * `message_info.json` `code`: register known info codes via JSON Schema `examples` array AND inline names in the description. Tooling (autocomplete, codegen) and humans both served. * docs: extract and document well-known info+warning mesages codes From the boyscout 'leave it better than you found it' rulebook... - Copy _error pattern as standalone file for _info and _warning - Include info and warning codes in reference docs * adopt WWW-Authenticate Bearer challenges Replace the pre-baked OAuth `continue_url` pattern with RFC 6750 §3 WWW-Authenticate challenges, plus RFC 9728 Protected Resource Metadata pointers. Resolves David's TC flag. Why: pre-baking an authorization URL forces the merchant to own parameters it can't sensibly own — PKCE code_challenge, state, redirect_uri, client_id are all client-side concerns. Native/agent clients per RFC 8252 construct their own authorization request anyway, so the pre-baked URL was either ignored or rewritten. Standard OAuth client libraries parse WWW-Authenticate Bearer challenges automatically; custom continue_url parsing was UCP-specific dead weight. * For Platforms: MUST process WWW-Authenticate Bearer challenges per RFC 6750 §3 on 401/403; extract scope parameter; SHOULD follow resource_metadata pointer per RFC 9728. Bumped Bearer Authorization bullet with RFC 6750 §2.1 reference. * For Businesses: MUST emit Bearer challenge on identity_required (401) and insufficient_scope (403). RFC 9728 SHOULD bullet upgraded to reference /.well-known/oauth-protected-resource and integration with WWW-Authenticate. * identity_required: full normative restructure (status code, header, body). realm MUST be issuer URI; error="invalid_token" when token present-but-bad; error SHOULD be omitted when no token (RFC 6750 §3.1). resource_metadata SHOULD. continue_url retained for non-OAuth onboarding flows ONLY; explicit MUST NOT for pre-baked OAuth URLs. * insufficient_scope: full normative restructure. realm + error + scope (full required set, not delta) MUST. resource_metadata SHOULD. Pre-baked OAuth continue_url removed entirely. * Security Considerations: new "Authentication challenges" bullet. Platforms MUST drive flow from structured scope/error params; error_description is hint-only and MUST NOT control flow. realm MUST match issuer URI for cross-protection-space correlation. * docs: add UCP and OAuth architecture explainer Document the architectural split between UCP and OAuth (RFC 8414) responsibilities that the rest of the spec relies on but never articulates. Anchors the four moving parts: * UCP config.scopes — hard gates (required auth) * OAuth scopes_supported — accepted scope vocabulary * Diff (scopes_supported ∖ config.scopes) — optional layer * UCP messages[] — runtime contextual hints (e.g., identity_optional) * Three corrections to identity-linking.md following review of the WWW-Authenticate Bearer changes: 1. Add no-token identity_required example The existing example only showed the token-present-but-expired case (error="invalid_token"). RFC 6750 §3.1 says error SHOULD be omitted when no token was presented — the more common case for a first request to a gated operation. Added a second labeled example for the no-token case so implementers aren't led to emit error="invalid_token" unconditionally. 2. Fix identity_optional section direction The section intro described identity_optional as "a mechanism for the platform to inform the buyer." The direction was reversed — the business emits this code in its response; the platform receives it and may present it to the user. Corrected to accurately describe the emitter and receiver. 3. Simplify identity_optional to remove misleading description coupling The section stated that per-scope description fields convey context for optional authentication, and that businesses SHOULD populate them when emitting identity_optional. This is incorrect: description is a field on scope_policy objects in config.scopes, which is the hard-gate (required) layer. Scopes relevant to identity_optional are by definition in the optional layer (scopes_supported ∖ config.scopes) and have no corresponding UCP schema field for descriptions. Removed the two-mechanisms paragraph and the SHOULD guidance on populating descriptions, as both described a mechanism that doesn't exist for optional-layer scopes. Upgraded identity_optional emission from MAY to SHOULD, since content on the message is the only available value prompt mechanism. --------- Co-authored-by: Amit Handa <amithanda@google.com> Co-authored-by: Ilya Grigorik <ilya@grigorik.com> --------- Co-authored-by: James Thompson <thompson.tomo@outlook.com> Co-authored-by: Guillaume V. <4216770+ptiper@users.noreply.github.com> Co-authored-by: Dario Guzik <dario@guzik.com.ar> Co-authored-by: Patrick R. Jordan <patrick.r.jordan@gmail.com> Co-authored-by: James Andersen <james.j.andersen@gmail.com> Co-authored-by: Nachiket Torwekar <nachiket.torwekar@gmail.com> Co-authored-by: Dylan Koch <dkoch74@gmail.com> Co-authored-by: Gauresh G Pai <107191770+gaureshpai@users.noreply.github.com> Co-authored-by: Greg Smith <smithgrg@amazon.com> Co-authored-by: Ilya Grigorik <ilya@grigorik.com> Co-authored-by: Amit Handa <amithanda@google.com> Co-authored-by: pemamian <pemamian@google.com> Co-authored-by: Nicholas James Hall <55357993+nicholasjameshall@users.noreply.github.com>
Our schema validation and lint infrastructure exercises the services / payload surface — request roundtrips, codegen, fixture validation. The profile / discovery schemas and the MCP openrpc meta block have no equivalent validation pass:
docs/specification/are not validated against their corresponding schemas.As a result, a few schema issues snuck in undetected. Worse, our published profiles schemas are effectively broken / can't be validated due to broken
oneOfand misplacedrequires. See individual commits for specificsservice.jsonplatform_schema: drop over-strictendpointrequirement0100ed4:profile_schema.json: top-leveloneOf→anyOf(was unsatisfiable)mcp.openrpc.json: remove stale RFC 7515 signature fieldTechnically, both oneOf and requires are breaking changes to the profile schemas, but they are un-breaking the current broken state, and so I propose we land and backport this to both 04-08 and previous releases.
Checklist
ucp-schematool (resolver, linter, validator). (Requires Maintainer approval)