feat: reserve the ucp protocol namespace, add map_order for registry ordering - #657
Open
igrigorik wants to merge 1 commit into
Open
feat: reserve the ucp protocol namespace, add map_order for registry ordering#657igrigorik wants to merge 1 commit into
ucp protocol namespace, add map_order for registry ordering#657igrigorik wants to merge 1 commit into
Conversation
UCP registries are reverse-DNS keyed JSON maps, and JSON object member order is not a protocol contract: RFC 8785 (JCS) canonicalization, used by UCP signing, sorts object member names. Businesses have no reliable way to declare preferred traversal order for registry keys, e.g. payment handler presentation order (#170, design discussion in #525). The model: the member name `ucp` is reserved at every object scope as the protocol namespace. The top-level envelope is the root manifestation of that reservation, not a special wrapper. The namespace is ambient: any object scope MAY carry a `ucp` member, domain schemas never declare it (it is document grammar, like the reservation itself), and its contents are defined exclusively by the vocabulary registered in ucp.json#/$defs/members. Consumers process the members they recognize and ignore the rest; a member is admitted to the vocabulary only if it is safe to ignore, so no member can be load-bearing for correctness. One exception: an object closed with additionalProperties:false must declare the optional `ucp` property explicitly. Future members register once and work at every scope immediately -- request constraints (#655) can rebase onto this vocabulary. The first member, map_order, declares key-traversal order for sibling map-valued fields, carried in an array because JCS preserves array element order. Partial lists are valid, unlisted keys remain valid and follow, and the list is not an allowlist. At the root envelope it orders the registries beside it: "ucp": { "payment_handlers": { "com.google.pay": [ ... ], "dev.shopify.shop_pay": [ ... ] }, "map_order": { "payment_handlers": ["dev.shopify.shop_pay", "com.google.pay"] } } At any deeper scope the same member rides the ambient `ucp` member, with no schema change to the host object -- e.g. ordering an identity provider registry inside a capability config: "config": { "providers": { "app.example.login": [ ... ], "com.google": [ ... ] }, "ucp": { "map_order": { "providers": ["app.example.login", "com.google"] } } } Refs #525, #170
This was referenced Jul 31, 2026
| the context of the cart (e.g., removing "Buy Now Pay Later" for subscription | ||
| items, or filtering regional methods based on shipping address). | ||
|
|
||
| **Presentation Order:** Businesses **MAY** declare a preferred presentation |
Contributor
There was a problem hiding this comment.
This is a tricky as per the comments on #170 and #176 citing that available instruments within handlers are really the presentation grain that a business may be most interested in ordering and map_order doesn't cover that (in current form). However, in practice this may still address a large number of use cases (where payment handlers are advertising more than one available instrument or where businesses are content to order at the handler level) riding on a broadly usable ordering mechanism.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
UCP represents extensible registries as JSON objects keyed by reverse-DNS names —
payment_handlers,services,capabilities. That shape is deliberate (decentralized governance, uniqueness by construction,propertyNamesenforcement), but JSON object member order is not a protocol contract: RFC 8785 canonicalization, which UCP signing relies on, sorts object member names. There is currently no reliable way for a Business to declare preferred traversal order for registry keys — the concrete driver is merchant-preferred payment-handler presentation order (#170; design discussion in #525).Proposed model
ucpis reserved at every object scope as the protocol namespace. The top-level envelope that profiles and responses already carry is not a special wrapper — it is the root manifestation of this reservation: a reserved member of the root object, grammatically identical to any nested scope carrying protocol annotations. Placement carries no semantics.ucpmember; domain schemas never declare it — the member is part of the UCP document grammar, like the name reservation itself. Its contents are defined exclusively by the vocabulary registered inucp.json#/$defs/members. Consumers process the members they recognize and MUST ignore the rest.additionalProperties: falsedoes not get an exemption from the grammar — it MUST declare the optionalucpproperty explicitly.Because registration is central, future members work at every scope the moment they land: #655's request constraints rebase onto this vocabulary as one property registration, immediately usable on any leaf object (e.g. per-line-item quantity constraints).
First member:
map_ordermap_orderdeclares preferred key-traversal order for sibling map-valued fields, carried in an array value because JCS preserves array element order while sorting object keys. Partial lists are valid; unlisted keys remain valid and follow via field-defined fallback or lexicographic order; the list is not an allowlist; absent means no order is declared, and consumers MUST NOT fall back to member order.At the root envelope, it orders the registries directly beside it:
{ "ucp": { "version": "2026-01-11", "payment_handlers": { "com.google.pay": [{ "id": "gpay", "version": "2026-01-11" }], "dev.shopify.shop_pay": [{ "id": "shop_pay", "version": "2026-01-11" }] }, "map_order": { "payment_handlers": ["dev.shopify.shop_pay", "com.google.pay"] } } }At any deeper scope, the same member rides the ambient
ucpmember — no schema change to the host object. Ordering an identity-provider registry inside a capability config:{ "config": { "providers": { "app.example.login": [{ "type": "oauth2" }], "com.google": [{ "type": "oauth2" }] }, "ucp": { "map_order": { "providers": ["app.example.login", "com.google"] } } } }For
payment_handlers, the declared order is the Business's presentation preference (#170): suggestive — the Platform SHOULD consider it and MAY apply its own ordering.Why this shape / alternatives considered in #525:
display_orderembedded rank (feat: add optional display_order to payment handlers #176): cannot attach to a map bucket whose value is an array; unnecessary where arrays already self-order.propertyNames— a breaking change for every shipped validator; typed maps become unions.payment_handlers_order): open-ended reserved-name sprawl;_ordersuffix collides with the commerce Order noun.$-prefixed member (original key ordering for UCP map registries #525 proposal): empirically taxed in codegen —$is an illegal identifier in Python/Go/Java/C#/Rust, and Pydantic's asymmetric alias defaults silently emit non-conforming payloads with stockdatamodel-code-generator.ucpis a clean identifier in every ecosystem, and the name is already de facto reserved (zero domain usages in the corpus).Compatibility
Additive. The envelope is open today; existing consumers ignore
map_order. No shipped registry is reshaped; registry maps stay pure and homogeneous (propertyNamesalready rejectsucpandmap_orderas registry keys — verified). Consumers that ignore the member lose nothing: order was never reliable before.Checklist