refactor: organize specification hierarchy by domain verticals (Shopping, Payment, Common) - #723
Conversation
…o dedicated directories
…w, and payment namespaces
9845d0a to
7737eb1
Compare
|
Overall, agree with the direction. Found a few issues to address before we ship... Local preview topologyThere are 29 redirect failures, which appear to come from the local preview not matching production. Production replaces the root Rec: Could we make the local preview reproduce production's alias before running Catalog-generated linksThree calls in Rec: Update those three arguments to Markdown failuresMarkdownlint reports three table-alignment failures in I like |
|
Thanks for the feedback. |
…lignments
- Parameterize schema URLs and capability identifiers with {{ ucp_version }} across core concepts, identity linking, permalink, and order docs
- Update method_fields and schema_fields macro reference paths in shopping catalog REST and MCP docs
- Align table columns in glossary Commerce section
- Fix Total heading and local fragment links in checkout and cart docs
- Mirror production specification symlink in build_local.sh for accurate local redirect validation
…port # Conflicts: # mkdocs.yml
…t-terms - Update relative links in fulfillment.md to point to shopping/catalog, shopping/checkout, and shopping/cart - Update relative links in payment-terms.md to point to payment/split-payments, overview/index, and shopping/checkout
- Format mkdocs.yml navigation and redirect_maps lines to be <= 80 characters - Wrap templated OpenRPC schema URL in order/mcp.md with angle brackets - Nest duplicate Request format heading under AP2 completion in checkout/a2a.md
igrigorik
left a comment
There was a problem hiding this comment.
Thanks for addressing earlier feedback. A few additional nits/suggestions, otherwise LGTM.
gsmith85
left a comment
There was a problem hiding this comment.
LGTM! Approving to land Phase 1 of RFC #520.
Context from Tech Council Deep Dive (2026-08-20): At today's TC Deep Dive, the council confirmed that payment constructs will remain in the common protocol namespace** (dev.ucp.common.* / dev.ucp.common.payments rather than dev.ucp.payments.*), reflecting payments as a shared horizontal capability across all
verticals.
For this PR, we are aligned on keeping docs/specification/payment/ as an organized, top-level documentation domain for developer discoverability (guides, actions, and handler examples). We will handle schema placement (source/schemas/common/) and any subsequent docs/nav path adjustments in #741 and follow-up PRs without blocking this restructure.
Non-blocking Nits / Consistency Cleanups
|
* Extend retail_location.json with more fields and move it out of shopping/ to generally represent any physical location (to be referenced by Location capability). * Add appropriate transition annotation to set proper expectation on field presence. * Add common location capability. * Minor wording updates in documentation and also fix rendering issues. * Minor fix on fulfillment retail location reference. * Fix on rendering links. * More fixes on rendering links. * Fix location_filter file name. * Clean up remaining broken links and minor style updates. * Address comments that involve minor schema changes. * Address rest of the comments from feedback. * Fix broken doc build link. * fix(location): mark custom filters as extensible Location search intentionally permits Business-defined filters, but the schema relied on JSON Schema's implicit open-object default while the prose named additionalProperties as the extension mechanism. Declare the extension point explicitly so schema readers and generated documentation can distinguish intentional extensibility from omission. Strict resolution remains a caller-selected closed-world override. * docs(location): remove catalog copy-paste Location documentation inherited Catalog-specific descriptions, rendering contexts, and a severity policy that Location never defined. It also documented a singular REST path and a filter name that do not exist in the binding/schema. Use Location-specific llms.txt descriptions and render scopes, remove the unsupported severity claim, and align the visible endpoint and filter names with their canonical definitions. * Add location into UCP glossary to resolve feedback on PR#642. * Add security & privacy considerations. * Address feedback on simplifying service area representation on location responses and finetune request filters. * Update hours representation to be more consistent with standard schema representation. * Relax exact quantity based search and instead leverage availability status based coarse search during discovery phase. * Standardize amenities vocabulary and also restructure filtering model between it and dynamic inventory filter. * Address bounded location representation problem and clean up misc/unused ucp annotation for fulfillment related files. * Fix broken reference rendering. * Fix example on REST to follow proper exception_hour representation. * Clean up legacy field description. * Fix signature definition in MCP JSONRPC transport schema definition. * Add validation rule for timezone in location.json. * Loosen language on what is being considered as a search inputs to give more flexible combination of request inputs. * Add some implementation guidance on how to deal with context-only search requests. * Add more validation on the filter schema and tighten up prose around how business should handle contextual hints fallback. * fix!: define deterministic operating hours for Location service (#687) * define deterministic operating hours The current Location PR introduces weekly and exceptional operating hours, but leaves several wire and evaluation semantics ambiguous. In particular, closures rely on an artificial midnight interval, `open_now` depends on an implicit server clock, exception date bounds are unclear, and the specification does not define timezone, overnight, DST, overlap, or precedence behavior. Close those gaps with a UCP-native schedule model informed by Schema.org's OpeningHoursSpecification: https://schema.org/OpeningHoursSpecification Schema.org is design input only. UCP owns the field names, values, and evaluation rules defined here. Make weekly intervals explicit and reusable: "hours": [ { "day": "tuesday", "opens": "09:00", "closes": "12:00" }, { "day": "tuesday", "opens": "13:00", "closes": "21:00" } ] Rename `open` and `close` to `opens` and `closes`, and define `day` as a stable UCP weekday identifier rather than localized display text. Multiple entries for one day represent split shifts, and an interval whose closing time is earlier than its opening time continues into the next local date. Refactor the shared time interval schema so `opens` and `closes` are an optional but inseparable pair. Weekly hours require both fields, while exception hours may omit both to represent a full closure. Reject the ambiguous `00:00` to `00:00` pair and reserve `00:00` to `23:59` as the full-local-day sentinel. Replace the previous exception shape: { "from": "2026-11-26", "through": "2026-11-27", "label": "Thanksgiving", "open": "00:00", "close": "00:00" } with inclusive local-date bounds and an actual closure representation: { "title": "Thanksgiving", "valid_from": "2026-11-26", "valid_through": "2026-11-26" } Rename `from`, `through`, and `label` to `valid_from`, `valid_through`, and `title`. Treat `title` as optional presentation metadata that does not affect schedule evaluation. Allow timed exceptions with paired `opens` and `closes`, including multiple entries with identical bounds for split shifts. Define every returned schedule in the Location's Business-owned IANA timezone. Require `timezone` whenever regular or exception hours are present, and keep the canonical schedule independent of the requesting Platform or Buyer's timezone. Specify deterministic evaluation: - convert an exact instant into each Location's local date, weekday, and time - use half-open timed intervals, except for the reserved full-day sentinel - let overnight intervals carry into the following local date - replace regular hours with exception hours at local midnight - treat omitted weekdays as having no interval starting that day - treat absent schedules as unknown rather than closed - evaluate DST gaps and folds pointwise without shifting nonexistent times - reject equal time pairs and intersecting non-identical exception ranges as Business conformance errors where JSON Schema cannot express the constraint Remove the redundant `open_now` filter. It makes results depend on an implicit processing clock and creates undefined precedence when combined with `open_at`. Require one caller-supplied RFC 3339 instant instead: "filters": { "hours": { "open_at": "2026-05-18T17:00:00Z" } } Require `open_at` to include `Z` or a numeric offset. The offset identifies the instant only; the Business still evaluates that instant using each candidate Location's authoritative IANA timezone. Keep the nested hours filter open so extensions can add qualifiers without changing the standard predicate. Move complete Search and Lookup examples into the transport-neutral capability documents. Cover hours with serviceability and amenities, inventory with distance, split shifts, full closures, and partial Lookup success there. Reduce REST and MCP examples to equivalent binding envelopes that link to the same canonical payload examples. This keeps both transports on equal footing, avoids duplicating domain semantics, and prevents one binding's examples from becoming more complete or authoritative than the other. Preserve MCP's required `meta["ucp-agent"].profile` contract while separating protocol metadata from the Location request. This is a breaking correction to the Location PR's draft wire shape: - `open` becomes `opens` - `close` becomes `closes` - `from` becomes `valid_from` - `through` becomes `valid_through` - `label` becomes `title` - `open_now` is removed - full closures omit both time fields instead of using `00:00` to `00:00` * s/weekday/day of week * clarify operating-hours semantics Define `open_at` as the caller-selected instant relevant to the request, such as an expected arrival or pickup time. This avoids framing it as a request for the Business's receipt-time notion of "now": normal request latency does not change the question, and the Business evaluates the supplied instant against each Location's schedule. Describe operating hours more directly as local dates and clock times interpreted using the Location's IANA timezone. Clarify that temporary closures retain the regular `hours` schedule and override it with a date-bounded `exception_hours` entry that omits `opens` and `closes`. Mirror omitted-schedule semantics in the Location schema for implementers who read generated references: - an omitted day has no regular interval beginning that day - an interval from the preceding day may still carry into it - omission of the entire `hours` property means the schedule is unknown Make `time_interval` genuinely reusable by limiting it to generic `HH:MM` opening and closing fields. Location-specific recurrence and timezone interpretation remain with the containing daily, exception, and Location schemas. Remove the schema check that rejected only `00:00`–`00:00`. The actual authoring rule rejects every pair where `opens` equals `closes`, but standard JSON Schema cannot compare sibling values; enforcing one special case would misleadingly imply that other equal pairs are valid. Continue enforcing paired field presence and time formatting mechanically, while keeping unequal times as a normative Business conformance requirement and requiring Platforms not to infer openness from invalid schedule data. * define authority for hours filtering The TC discussion converged on keeping one `open_at` filter, but left open whether both the Platform and Business could apply timing tolerance when interpreting immediate intent. After further consideration, assign that flexibility to one side only. The Platform owns the interpretation of Buyer intent and selects the instant to query. It may use its current time, choose an expected arrival, pickup, or order-acceptance time, and round or adjust that choice to the granularity appropriate to the interaction. Once encoded, however, `open_at` identifies one specific RFC 3339 instant. Require the Business to evaluate that instant exactly as supplied using each Location's authoritative timezone. It must not round, shift, substitute request receipt time, or otherwise reinterpret the value. Allowing both parties to apply independent tolerance would make the evaluated question unknowable and could produce different matches for identical requests near an opening or closing boundary. Apply normal positive-match filter semantics: return a Location only when the Business can establish that it is open at `open_at`. Missing, invalid, out-of-range, or otherwise unusable schedule data is a non-match rather than a reason to guess or adjust the requested instant. Clarify that the numeric offset in `open_at` identifies the queried instant, not the Location's timezone. The Business converts that instant using the Location's authoritative IANA timezone before evaluating its local schedule. State closing-boundary behavior concretely: a `10:00`–`17:00` interval is open immediately before `17:00` and closed at `17:00`. This avoids ambiguity over whether `HH:MM` values represent exact boundaries or minute-sized buckets. Keep exception payloads useful for planning without accumulating stale history. Businesses should remove entries once they cannot affect any current or future instant and publish known future exceptions through the horizon for which their schedule is authoritative. Remove the request-language localization recommendation for exception `title`. The field remains optional presentation metadata, but this capability does not define a localization guarantee for it. * Cleanup incorrectly placed signature headers in meta object definition. * Address feedback on existing contracts consistency. * Remodel amenities as reverse-DNS string arrays. * Fix documentation examples. * Fix serves(target) contextual fallback algorithm. * Revert back the changes to retail_destination.json to decouple the scope. * Remove transition annotation from location_base.json as this is now being treated as a net new schema type. * fix!: Location spatial relations and Lookup correlation (#753) * separate location spatial relations from filters The previous filters.geo shape mixed predicates about a Location with relations to Platform-supplied points and addresses. That made context fallback ambiguous and coupled proximity with serviceability even when they need different operands. Promote distance and serves to independent request-root relations in Search and Lookup. Keep filters for inherent or current Location facts, preserve its open extension model, and combine every explicit relation and predicate with AND. Keeping the relations separate allows a request to measure distance from one point while testing serviceability to another, without hidden operand inheritance. Require distance.center plus inclusive distance.max in meters and define matching against the unrounded shortest WGS 84 ellipsoidal geodesic. Reject unsupported radii rather than silently clamping, substituting operands, or falling back to context, signals, or IP-derived locality. Model serves as exactly one point, coarse locality, or negotiated reverse-domain target. Treat a match as provisional evidence that at least one currently available method can serve the target, without exposing coverage geometry or promising checkout success. Reject targets that cannot be evaluated rather than ignoring them or broadening results. Keep query and contextual hints non-authoritative: they may influence ranking or bounded selection but cannot create or relax spatial proof. Preserve empty, hint-only, pagination-only, and filters-only Search requests as bounded browse forms, with the existing default page size of 10. * mirror Catalog correlation in Location Lookup Location Lookup supports secondary identifiers and aliases, but returning only the canonical Location.id makes unordered batch results ambiguous when inputs converge on one Location or fan out to several. Mirror Catalog's lookup API shape by requiring inputs[] on every returned Location. Each entry preserves one identifier exactly as requested; a Location resolved by multiple inputs is returned once with all correlations, while one input may resolve to multiple Locations. Requiring inputs for direct ID matches adds minor payload overhead but preserves one uniform, schema-enforceable rule across Catalog and Location. Keep the correlation record inline and omit Catalog's match classification because Location has no product-to-featured-variant resolution distinction. Businesses must support canonical Location.id values and may additionally support aliases or secondary identifiers. Apply batch limits after deduplication. Process the first N distinct identifiers in request order and return a successful partial response with a batch_limit_applied informational message, leaving the omitted suffix retryable rather than reporting it as unresolved or failing at the transport layer. * drop geo disclosure for distance matches * Minor tweaks to language & remove unnecessary ucp_request annotation from fields that are already required in the enclosing type. * make pagination defaults Business-defined The shared pagination schema fixed `limit.default` at 10, while Catalog and Location described different omission behavior. This made one shared type carry divergent semantics and could cause clients or generated SDKs to materialize 10 before the Business could apply its own policy. Require every Business to apply a default page size when `limit` is omitted, recommend 10 without making it a fixed value or supported floor, and allow the Business to choose another default. Requested and default page sizes remain targets rather than guaranteed result counts, so a Business may return fewer results when enforcing its maximum and a Platform must not assume count equality. Platforms that need a particular page size can continue to send an explicit `limit`. Remove the JSON Schema `default` annotation so the wire schema does not misrepresent a Business-specific policy. Align Catalog and Location on the shared contract, and have REST and MCP conformance link to the operation-level pagination rules instead of duplicating a default value. Keep Location's spatial semantics independent: pagination flexibility never permits reducing or silently clamping an explicit `distance.max`. * clarify Location spatial constraint wording Attach the context-signal prohibition to the Business actor and separate that normative obligation from the factual consequence: contextual hints may influence ranking or bounded selection, but prove neither proximity nor serviceability and cannot replace explicit spatial operands. Remove the self-referential "`max` is not an alias" sentence from the distance schema. `max` is already the canonical required property, and documenting abandoned candidate names adds design-history noise without strengthening the contract. Preserve the existing requirements to evaluate the supplied radius exactly and reject requests whose radius cannot be honored rather than clamping or substituting another value. --------- Co-authored-by: Jing Li <jingyli@google.com> * Refactor Location specification documentation to follow the same pattern #723 is enforcing. * Fix broken links. * Address outstanding feedback around using cosolidated location_summary.json representation and normative amenity language. * Fix outstanding broken references. * Minor tweaks to the language to maintain consistency. * Fix rendering link issues. * Add location response schame in ucp.json and correct the last missing deeplink to lookup location. * Fix one more typo on the rendering. * Address feedback. * fix!: make Location amenities self-describing (#765) * make Location amenities self-describing Amenity identifiers use an open reverse-DNS vocabulary, so Platforms cannot derive buyer-facing text or safely present unfamiliar Business-defined amenities from the key alone. Replace amenity arrays with maps whose values require a short, buyer-facing description. Define exact-key filter matching and require filtered results to disclose requested keys so Platforms can verify each match. The presentation contract recommends presenting every returned amenity from Business-provided text, permits enhanced UX for known identifiers, and prohibits allowlist filtering or inferred semantics for unknown identifiers. * clarify Location fulfillment handoff A Location ID identifies a destination, not a fulfillment mode. Treating it as a pickup signal conflated destination selection with the method contract and could make discovery results appear binding. Document that Platforms submit Location IDs only on applicable methods, whose type determines the mode. Businesses revalidate current availability and terms; recognizing an ID neither reserves inventory nor guarantees eligibility. * repair Location routes and generated references Location documentation moved under specification/common/location, but discovery examples and MkDocs metadata still referenced the old paths. The REST binding also linked to a Lookup Location entity that it did not render. Point capability links and route metadata at the canonical Location paths. Add the generated Lookup Location section so its anchor and schema fields are available, and refresh route summaries to match the current Location model. * align Location terminology and metadata Location documentation and bindings mixed legacy actor names with stale schema metadata. The rich Location schema also described a removed Base Location layer, while location destinations retained metadata used only by a deleted generator. Use glossary actor terms consistently across documentation and service bindings, normalize Location and Lookup naming, and make independent capability adoption an explicit BCP 14 permission. Describe the rich Location as composing Location Summary and remove the final retired ucp_shared_request marker. * remove idempotency from read-only Location tools Location Search and Lookup are read-only operations, but their MCP metadata exposed an optional idempotency key borrowed from mutating operations. That implied retry-deduplication and replay semantics the capability does not define. Remove the parameter so retries follow ordinary read-only request semantics. The REST binding already carries no equivalent idempotency requirement. * review feedback on the self-describing amenities change: - Amenity presentation (index.md): adopt Platform presentation autonomy — a Platform MAY decide whether and where amenities appear; the contract binds only when it presents them. Keep identifier-based suppression at MUST NOT (review proposed SHOULD NOT): self-description exists so unrecognized identifiers carry no presentation penalty, and weakening it would make extension amenities second-class — the footgun this PR closes. Adopt the reviewer's "solely because unrecognized" scoping, which permits uniform policies such as truncation. - Stable Identifiers (index.md): stop restating Fulfillment's contract in Location docs. The bullet is now informative — a Location ID selects only the physical entity — and defers to fulfillment.md's Selection and Location Identity, which owns selected_destination_id semantics including revalidation. - Amenity filter rejection (search.md): reword the Platform rejection sentence for flow; semantics unchanged. - Search example prose (search.md): group the hours sentences by moving the custom-amenity note after the Operating Hours reference; the sentence stays because the example deliberately shows a namespaced custom amenity remaining presentable. * fix: replace Location inventory filtering with item availability (#766) * fix!: replace Location inventory filtering with item availability The inventory filter mixed stock, order-acceptance, lifecycle, and timing semantics in an open per-item status vocabulary, while Location returns only aggregate Location matches. Replace it with `filters.items`, a nonempty array of distinct, Business-scoped item identifiers shared by Search and Lookup. A Location matches only when every item is available there under the Business's current data; unknown, unavailable, or non-evaluable items are ordinary non-matches. Keep hours, quantity, and fulfillment methods outside this predicate. Catalog with Fulfillment describes item-level methods for a shortlisted Location, Cart carries configuration and quantity, and Checkout with Fulfillment revalidates basket feasibility and final terms. * tighten Location availability framing Restore the distinction between root spatial relations and filters over Location facts, and keep Common Location guidance independent of Shopping-specific handoffs. Use consistent spatial terminology and stop enumerating open filter fields in shared schema and service descriptions, reducing drift without changing filters.items behavior. * Remove unused wording (geofence) from the custom-words ignore list as part of final cleanup. * Final reference fix post refactoring merge. --------- Co-authored-by: Ilya Grigorik <ilya@grigorik.com>
Description
This PR builds on @jingyli 's RFC #520 specifically addressing Phase 1. Please note there's changes in this implementation from the original RFC, however the intent remains the same.
Summary
This PR refactors the specification documentation structure by grouping specification documents into dedicated, domain-aligned namespaces and vertical directories (
shopping/,payment/,common/, andoverview/). This allows us to add more verticals in the future along the same hierarchy.It aligns the documentation information architecture with the UCP protocol taxonomy (
{reverse-domain}.{service}.{capability}), simplifies navigation, updates all internal links and macros, and implements comprehensive backwards-compatible redirects for legacy URLs.Key Changes & Restructuring
1. Shopping Vertical (docs/specification/shopping/)
• Checkout Capability: Moved to shopping/checkout/ (index.md, rest.md, mcp.md, a2a.md, embedded.md).
• Cart Capability: Moved to shopping/cart/ (index.md, rest.md, mcp.md, embedded.md).
• Catalog Capability: Moved to shopping/catalog/ (index.md, search.md, lookup.md, rest.md, mcp.md).
• Order Capability: Moved to shopping/order/ (index.md, rest.md, mcp.md).
• Playground: Relocated from specification/playground.md to specification/shopping/playground.md.
2. Payment Vertical (docs/specification/payment/)
• Core Guides & Protocols: Moved to payment/ (guide.md, template.md, tokenization.md, authentication.md, split-payments.md).
• Payment Actions: Grouped into payment/actions/ (device-data-collection.md, three-ds-challenge.md).
• Payment Examples: Grouped into payment/examples/ (processor-tokenizer-payment-handler.md, platform-tokenizer-payment-handler.md, encrypted-credential-
payment-handler.md).
3. Common Vertical (docs/specification/common/)
• Identity Linking: Moved to common/identity-linking/index.md to establish the domain-agnostic common services namespace.
4. Overview (docs/specification/overview/)
• Moved overview.md to overview/index.md for consistent directory-style routing.
5. Navigation & LLM Indexing (mkdocs.yml)
• Sidebar Navigation: Reorganized nav -> Specification into symmetrical top-level vertical groupings:
• Overview
• Common
• Shopping
• Payment
• Signatures
• Reference
• llmstxt Plugin: Synchronized all section headings and path mappings to preserve accurate LLM context indexing for AI agents.
6. Backward Compatibility & Redirects
• Configured 29 explicit mappings in plugins.redirects.redirect_maps so that all legacy paths (e.g. /specification/checkout.md, /specification/payment-
handler-guide.md, /specification/playground.md) cleanly forward to their new vertical endpoints without 404s.
---
Category (Required)
Please select one or more categories that apply to this change.
ucp-schematool (resolver, linter, validator). (Requires Maintainer approval)Related Issues
#520
Checklist
!for breaking changes).Screenshots / Logs (if applicable)