Skip to content

chore(ci): update workflow path triggers and improve docs build#4

Open
sakinaroufid wants to merge 28 commits intomainfrom
pr-c-workflow-cleanup
Open

chore(ci): update workflow path triggers and improve docs build#4
sakinaroufid wants to merge 28 commits intomainfrom
pr-c-workflow-cleanup

Conversation

@sakinaroufid
Copy link
Owner

@sakinaroufid sakinaroufid commented Mar 1, 2026

Description

Improves existing CI workflows:

linter.yaml:

  • Adds paths filter so the linter workflow only triggers on relevant file changes instead of every PR
  • Reformats FILTER_REGEX_EXCLUDE to multiline YAML for readability

docs.yml:

  • Updates path trigger from requirements-docs.txt to pyproject.toml + uv.lock
  • Changes uv sync to uv sync --all-groups to install all dependency groups
  • Adds explicit Rust toolchain (dtolnay/rust-toolchain@stable) and cargo caching (Swatinem/rust-cache@v2) before the existing cargo install ucp-schema step

Other:

  • Adds EDITMSG to .cspell/custom-words.txt
  • Minor comment reordering in .pre-commit-config.yaml (prettier hook)

This is part 3 of 3 from Universal-Commerce-Protocol#13, split per maintainer request.

Type of change

  • New feature (non-breaking change which adds functionality)

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • My changes generate no new warnings
  • New and existing unit tests pass locally with my changes

@github-actions
Copy link

github-actions bot commented Mar 1, 2026

Super-linter summary

Language Validation result
BIOME_LINT Pass ✅
GITHUB_ACTIONS Pass ✅
GIT_MERGE_CONFLICT_MARKERS Pass ✅
PRE_COMMIT Pass ✅
YAML Pass ✅

All files and directories linted successfully

For more information, see the
GitHub Actions workflow run

Powered by Super-linter

@sakinaroufid sakinaroufid reopened this Mar 1, 2026
@github-actions
Copy link

github-actions bot commented Mar 1, 2026

Super-linter summary

Language Validation result
BIOME_LINT Pass ✅
GITHUB_ACTIONS Pass ✅
GIT_MERGE_CONFLICT_MARKERS Pass ✅
PRE_COMMIT Pass ✅
YAML Pass ✅

All files and directories linted successfully

For more information, see the
GitHub Actions workflow run

Powered by Super-linter

@sakinaroufid sakinaroufid force-pushed the pr-c-workflow-cleanup branch from bd97392 to a322c67 Compare March 1, 2026 16:46
@github-actions
Copy link

github-actions bot commented Mar 1, 2026

Super-linter summary

Language Validation result
BIOME_LINT Pass ✅
GITHUB_ACTIONS Pass ✅
GIT_MERGE_CONFLICT_MARKERS Pass ✅
PRE_COMMIT Pass ✅
TRIVY Pass ✅
YAML Pass ✅

All files and directories linted successfully

For more information, see the
GitHub Actions workflow run

Powered by Super-linter

dwdii and others added 3 commits March 2, 2026 09:35
…ommerce-Protocol#233)

* fix: Resolve broken assets when a space was in the name.

* chore: Linter adjustments.
…-Protocol#210)

* Adding currency field to top level of Order
* Improving the currency description in Order
* Update currency field description in source/schemas/shopping/order.json

Co-authored-by: Ilya Grigorik <ilya@grigorik.com>
@github-actions
Copy link

github-actions bot commented Mar 4, 2026

Super-linter summary

Language Validation result
BIOME_LINT Pass ✅
GITHUB_ACTIONS Pass ✅
GIT_MERGE_CONFLICT_MARKERS Pass ✅
JSON Pass ✅
PRE_COMMIT Pass ✅
PYTHON_RUFF Pass ✅
PYTHON_RUFF_FORMAT Pass ✅
TRIVY Pass ✅
YAML Pass ✅

All files and directories linted successfully

For more information, see the
GitHub Actions workflow run

Powered by Super-linter

alexpark20 and others added 19 commits March 6, 2026 16:39
…iversal-Commerce-Protocol#216)

* ucp.status discriminator, unrecoverable severity

  - Add `status` field ("success"|"error") to ucp.json $defs/base as
    application-level operation discriminator (optional, default "success")
  - Constrain error_response.json to require ucp.status="error" via allOf
  - Add `unrecoverable` as fourth severity value to message_error.json
  - Remove bare 404 from GET /carts/{id} in rest.openapi.json (not_found
    is a business outcome returned as 200+error_response)
  - Fix all error examples across 7 doc files: add ucp.status:"error",
    add severity:"unrecoverable" to out_of_stock/not_found/version_unsupported
  - Fix checkout.md: add unrecoverable to severity table, correct phantom
    "severity: escalation" in business guidelines
  - Fix overview.md: version_unsupported examples now use error_response
    envelope shape

Co-authored-by: Alex Park <alex.park@shopify.com>

---------

* cosmetic cleanup

  Added severity guidance for error responses: SHOULD use recoverable or
  unrecoverable when no resource exists; requires_* severities assume a
  living resource.

  Cleanup:
  - Remove stale "Cart exists (200) or doesn't (404)" from cart.json
  - Strip trailing periods from message content examples
  - Wrap long lines in checkout.md and cart.md to ~80 chars

* fix(errors): clarify resource state vs action

  Severity reflects two things: the resource state and the recommended
  action. The first three values (recoverable, requires_buyer_input,
  requires_buyer_review) apply when a resource exists — they differ on
  who acts. `unrecoverable` applies when no valid resource exists — the
  response contains ucp.status: "error" with no resource in the body.

  This is a clean partition: resource exists → severity tells you how to
  fix it; no resource → severity is unrecoverable, retry with new
  resource or inputs. Error responses MUST use severity: "unrecoverable".

  The transport/business error boundary follows the same principle:
  discovery failures are transport errors (inputs could not be retrieved
  or were malformed); negotiation and handler failures are business
  outcomes (handler executed on provided inputs and reported the result).

* fix review feedback

  - Add continue_url hand off as platform action for unrecoverable
    severity in table, consistent with processing algorithm
  - Fix missing period between requires_buyer_review and unrecoverable
    descriptions in message_error.json schema

---------

Co-authored-by: Ilya Grigorik <ilya@grigorik.com>
…otocol#169)

Bumps [pillow](https://github.com/python-pillow/Pillow) from 11.3.0 to 12.1.1.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](python-pillow/Pillow@11.3.0...12.1.1)

---
updated-dependencies:
- dependency-name: pillow
  dependency-version: 12.1.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Guillaume V. <4216770+ptiper@users.noreply.github.com>
Co-authored-by: NanL3001 <nearlyforget2012@gmail.com>
Co-authored-by: Ryan C <cheungr@google.com>
* fix/clarify version negotiation

  Prior spec required businesses to process any request from a platform
  at an older protocol version ("MUST process if platform ≤ business").
  This imposed an unbounded backwards-compatibility obligation — every
  business must support every historical version indefinitely.

  New model: the business's protocol version is canonical. Businesses
  declare accepted versions via a `supported_versions` array in their
  profile (defaults to [version] when omitted). Platforms MUST support
  the business's declared version to initiate a request. The business
  validates the platform's version against its supported_versions list
  and rejects with 422 if there is no match.

  This also reclassifies version_unsupported from a negotiation result
  (HTTP 200 with checkout message schema) to a protocol error (HTTP 422),
  since version mismatch is a pre-negotiation gate check — the buyer
  cannot resolve a protocol incompatibility.

* version selection step to capability intersection

  The intersection algorithm matched capabilities by name only, without
  specifying how versions are resolved when multiple are present. Add
  explicit step: compute mutual version set, select highest, exclude
  capability if no mutual version exists.

* version discovery: supported_versions URI map

  Protocol and capability versions are independent axes — a flat profile
  cannot express which capabilities are available at which protocol version.
  supported_versions becomes a map from protocol version to profile URI,
  where each URI points to a complete, self-contained profile for that
  version. Platform bootstrap: fetch /.well-known/ucp, match version or
  follow the URI in supported_versions, get an unambiguous capability set.

  Key design choices:
  - uri-reference format allows relative paths (/.well-known/ucp/2026-01-11)
  - Current version served by the root profile; only older versions in map
  - Version-specific profiles are leaf documents (no supported_versions)
  - Both platform and business obligations explicit at version mismatch

* min_protocol_version for extension schemas

  Extension schemas SHOULD declare min_protocol_version to indicate the
  minimum UCP protocol version they require. This makes the protocol
  dependency explicit and verifiable, rather than relying on profile
  publishers to self-attest compatibility.

  The schema author declares the requirement; the profile publisher
  selects and advertises compatible versions. When present, platforms
  and businesses SHOULD verify negotiated version >= min_protocol_version
  during schema resolution. Incompatible extensions are excluded from
  the active capability set with orphan re-pruning.

  Changes:
  - New "Protocol Version Constraint" subsection under Extension Schema
    Pattern with field definition, example, and verification contract
  - Resolution Flow gains step 4 (Protocol Compatibility) between
    Schema Fetch and Compose

* address PR review feedback on version negotiation

  - Clarify "current" → "the protocol version it declares"
  - Upgrade SHOULD → MUST for min_protocol_version validation
  - Cross-reference capabilities_incompatible error in request-time
    validation flow

* version lifecycle is business policy

  Protocol does not prescribe deprecation schedule.

* requires: unified version constraints

  Replace min_protocol_version with a requires object that captures
  both protocol and capability version constraints. Extension schemas
  can now declare:

    "requires": {
      "protocol": { "min": "2026-01-23" },
      "capabilities": {
        "dev.ucp.shopping.checkout": { "min": "2026-06-01" }
      }
    }

  Why capability version constraints: 3P extensions move on their own
  release schedule. When com.acme.loyalty extends dev.ucp.shopping.checkout,
  it depends on the semantic contract of a specific checkout version.
  Structural schema validation (allOf composition) catches missing fields,
  but cannot catch semantic changes — e.g., a field changing units, enum
  values shifting meaning, or behavioral contracts tightening. The
  extension author declaring "I understand checkout's contract as of
  version X" is the only defense against this class of silent bugs.

  Why min/max: extensions should be able to declare both a floor (the
  oldest version whose semantics they depend on) and a ceiling (the
  newest version they've verified against). An extension tested against
  checkout 2026-01-23 through 2026-09-01 should not silently claim
  compatibility with 2026-12-01 if a breaking change landed.
…ure (Universal-Commerce-Protocol#234)

* chore: update CODEOWNERS with devops-maintainers for tooling and generated files

* chore: update documentation ownership in CODEOWNERS

* Update CODEOWNERS with hierarchical authority and Governance inclusion
…ry (Universal-Commerce-Protocol#55)

* feat(catalog): Catalog capability for product discovery

Introduces `dev.ucp.shopping.catalog` capability enabling platforms to search
business product catalog and perform targeted product+variant lookups.

Checkout capability assumes the platform already knows what item to buy (via
variant ID). Catalog capability fills the discovery gap—enabling scenarios like
"find me blue running shoes under $150" that lead to cart building and checkout.

  Product (catalog entry)
    ├─ id, title, description, url, category
    ├─ price: PriceRange (min/max across variants)
    ├─ media[]: images, videos, 3D models (first = featured)
    ├─ options[]: dimensions like Size, Color
    ├─ variants[]: purchasable SKUs (first = featured)
    ├─ rating: aggregate reviews
    └─ metadata: merchant-defined data

  Variant (purchasable SKU)
    ├─ id: used as item.id in checkout
    ├─ sku, barcode: inventory identifiers
    ├─ title: "Blue / Large"
    ├─ price: Price (amount + currency in minor units)
    ├─ availability: { available: bool }
    ├─ selected_options[]: option values for this variant
    ├─ media[], rating, tags, metadata
    └─ seller: optional marketplace context

  - Free-text query with semantic search support
  - Filters: category (string), price (min/max in minor units)
  - Context: country, region, postal_code, intent
  - Cursor-based pagination (default 10, max 25)

  - Accepts product ID OR variant ID
  - Always returns parent product with context
  - Product ID → variants MAY be representative set
  - Variant ID → variants contains only requested variant
  - NOT_FOUND returns HTTP 200 with error message (not 404)

Location and market context unified into reusable types/context.json:

  {
    "country": "US",      // ISO 3166-1 alpha-2
    "region": "CA",       // State/province
    "postal_code": "..."  // ZIP/postal
  }

Catalog extends with 'intent' for semantic search hints.

REST:
  POST /catalog/search     → search_catalog
  GET  /catalog/item/{id}  → get_catalog_item

MCP (JSON-RPC):
  search_catalog
  get_catalog_item

* Extract search filters to standalone schemas

  Inline object definitions in search_request.filters weren't rendered
  in generated docs (showed as plain "object" without properties).

  Fix by extracting to referenceable schemas:
  - search_filters.json: category + price filter definitions
  - price_filter.json: min/max integer bounds (distinct from price_range
    which uses full Price objects with currency)

* add custom words for spellcheck

* rebase on main

* split into search and lookup capabilities

  - dev.ucp.shopping.catalog.search
  - dev.ucp.shopping.catalog.lookup

  Docs:
  - Restructure to catalog/ directory
  - index.md: shared concepts (Product, Variant, Price, Messages)
  - search.md, lookup.md: individual capability docs
  - rest.md, mcp.md: transport bindings

* clarify context & market assignment

* feat(context): add language field for content localization

  Add `language` to shared Context type for requesting localized content.
  Uses IETF BCP 47 language tags (same format as HTTP Accept-Language).

  Key design points:
  - For REST, platforms SHOULD fall back to Accept-Language header when
    field is absent; when provided, context.language overrides header
  - Same provisional hint pattern: businesses MAY return different
    language if requested language unavailable
  - Shared across catalog, checkout, cart (applies to buyer journey)

* Add POST /catalog/lookup endpoint + context query params for GET

  Support both endpoints for consistency with cart/checkout:
  - GET /catalog/item/{id}?country=US&region=CA&language=es - location context
    via query params, language overrides Accept-Language header
  - POST /catalog/lookup - full context in body (including sensitive `intent`)

  Context is extensible, but only a well-known subset (country, region,
  postal_code, language) is supported via GET query params. POST supports
  the complete context object.

  Also normalizes error codes to lowercase snake_case for consistency with
  checkout (NOT_FOUND → not_found, DELAYED_FULFILLMENT → delayed_fulfillment).

* security hardening, currency support, schema refinements

Context security (catalog, checkout, schema):
- Clarify context signals are provisional hints—not authorization
- Enforcement MUST occur at checkout with authoritative data
- May be ignored if inconsistent with stronger signals (fraud rules,
  export controls, authenticated account)

Currency support:
- Add optional `currency` field to context for multi-currency markets
- Supports scenarios like Switzerland (CHF default, EUR preference)
- Added to REST GET query params and schema

Security:
- Add HTML sanitization guidance to product/variant descriptions
- Platforms MUST strip scripts, event handlers, untrusted elements

Schema refinements:
- rating.json: add `scale_min` with default: 1
- price_filter.json: clarify context→currency determination
- product.json: improve `handle` description for SEO vs API usage

Consistency:
- Error codes normalized to lowercase snake_case (not_found)
- Fix invalid product example (missing description, price)
- Update media/variants ordering language

* fix linter issues

* rename `price` fields on Product for clarity

  Product.price → Product.price_range
  Product.list_price → Product.list_price_range

  The fields reference PriceRange schema (min/max), so naming should
  reflect this. Variant.price and Variant.list_price remain unchanged
  as they reference single Price (not range).

* extensible category schema with taxonomy support

  Introduce category.json schema to support multiple product
  taxonomies (google_product_category, shopify, merchant).

  Schema changes:
  - New: category.json with {value, taxonomy} structure
  - Product: category string → category[] (Category[])
  - Variant: category string → category[] (Category[])

* refactor to POST-only batch lookup API

  Allow batch (multi ID) lookup support. Enables single and multi
  item lookup and aligns request and response shapes to search.

  Discussed at TC, agreed on POST-only:
  - Keeps request/response modeling symmetric (context in body)
  - Avoids special-casing query params for extensible context fields
  - Simplifies overall protocol surface
  - Single item GET can be added later if necessary

  Updated API:
  - REST: `POST /catalog/lookup` accepts `ids` array + `context` object
  - MCP: `catalog.ids` inside catalog object (matches search pattern)
  - Response returns `products` array (symmetric with search)

  Identifier flexibility:
  - MUST support product ID and variant ID
  - MAY support secondary identifiers (SKU, handle, etc.)
  - Secondary identifiers must be fields on returned product object
  - Client correlates results by matching fields (no guaranteed order)

* Remove stale spec/ directory (schemas live in source/)

* update category examples with multi-taxonomy response

* fix examples to show price_range on Product

* extract description into reusable type

* feat: category search filter as an array

  Product.category is already an array of {value, taxonomy} objects, but
  the search filter only accepted a single string — making it impossible
  to filter by multiple categories in one request. Align the filter type
  with the product field: accept an array of strings with OR semantics.

* feat: variant.input[] correlation for batch lookup

  Batch lookup accepts N identifiers and returns M grouped products.
  Without explicit correlation, clients must reverse-engineer which
  request identifiers mapped to which response variants by field-matching
  (comparing returned IDs, SKUs, handles against what was sent). This is
  brittle, ambiguous for secondary identifiers, and silent about match
  semantics.

  Add an `input` array to each variant in lookup responses. Each entry
  carries the originating request identifier (`id`, required) and an
  optional `match` type indicating resolution semantics:

  - Product ID in, featured variant out → match: "featured"
    (server selects a representative variant)
  - Variant ID in, exact variant out → match: "exact"
    (input directly identifies this variant)
  - Multiple inputs resolve to same product → one product, each
    matched variant carries its own input entries
  - Mixed product + variant IDs → both match types coexist in
    the same response

  ---
  SUPPORTED IDENTIFIERS
    R1. MUST support product ID and variant ID; MAY support secondary (SKU, handle)
    R2. Duplicate IDs → MUST deduplicate
    R3. One ID → multiple products: return matches, MAY limit result set
    R4. Multiple IDs → same product: MUST return once

  CLIENT CORRELATION
    R5. Response does not guarantee order
    R6. Each variant carries input[] identifying which request IDs resolved to it
    R7. Multiple IDs → same variant: one entry per ID, each with own match type
    R8. Variants without input[] MUST NOT appear in lookup responses

  RESOLUTION BEHAVIOR
    R9. exact: identifier resolved directly to this variant (variant-level)
    R10. featured: identifier resolved to parent product, server picked variant (product-level)

  ---
  A. Simple product ID — ["prod_abc"]
  R1  → product ID supported ✓
  R10 → product-level resolution → featured
  R8  → variant must have input
  → 1 product, 1 variant
    variant.input: [{id: "prod_abc", match: "featured"}]

  B. Simple variant ID — ["var_xyz"]
  R1  → variant ID supported ✓
  R9  → variant-level resolution → exact
  R8  → variant must have input
  → 1 product, 1 variant
    variant.input: [{id: "var_xyz", match: "exact"}]

  C. Two variant IDs, same product — ["var_abc_10", "var_abc_11"]
  R1  → variant IDs supported ✓
  R4  → same product → returned once
  R9  → both variant-level → exact
  R8  → each variant must have input
  → 1 product, 2 variants
    var_abc_10.input: [{id: "var_abc_10", match: "exact"}]
    var_abc_11.input: [{id: "var_abc_11", match: "exact"}]

  D. Product ID + its own variant ID (overlap) — ["prod_abc", "var_abc_10"]
  R4  → same product → returned once
  R10 → prod_abc is product-level → featured variant
  R9  → var_abc_10 is variant-level → exact

  If featured ≠ var_abc_10:
    → 1 product, 2 variants
    featured.input: [{id: "prod_abc", match: "featured"}]
    var_abc_10.input: [{id: "var_abc_10", match: "exact"}]

  If featured = var_abc_10:
    R7 → multiple IDs resolve to same variant, one entry per ID
    → 1 product, 1 variant
    var_abc_10.input: [
      {id: "var_abc_10", match: "exact"},
      {id: "prod_abc", match: "featured"}
    ]

  E. SKU shared across products — ["SKU-SHARED"]
  R1  → SKU MAY be supported
  R3  → matches multiple products → return matches, MAY limit
  R9  → SKU resolves directly to variant → exact
  → N products (server's discretion on N), each with 1 variant
    each variant.input: [{id: "SKU-SHARED", match: "exact"}]

  F. Handle (product-level) — ["blue-runner-pro"]
  R1  → handle MAY be supported
  R10 → handle resolves to product → featured
  → 1 product, 1 variant
    variant.input: [{id: "blue-runner-pro", match: "featured"}]

  G. Duplicate identifiers — ["prod_abc", "prod_abc"]
  R2  → deduplicated to ["prod_abc"]
        → then Scenario A
  → 1 product, 1 variant
    variant.input: [{id: "prod_abc", match: "featured"}]

  H. Mixed batch — ["prod_abc", "var_xyz"] (different products)
  R1  → both supported ✓
  R10 → prod_abc is product-level → featured
  R9  → var_xyz is variant-level → exact
  → 2 products
    prod_abc's variant.input: [{id: "prod_abc", match: "featured"}]
    var_xyz's variant.input: [{id: "var_xyz", match: "exact"}]

  I. Not found — ["prod_nonexistent"]
  R1  → product ID supported ✓
        → no match → product not in response
        → response MAY include not_found message (per index.md)
  → 0 products, optional message

* fix: update catalog to RFC 9421 signing parameters

  The signatures commit (0426800) replaced the single request_signature
  header with the RFC 9421 trio (Signature, Signature-Input,
  Content-Digest) across all endpoints.

* fix: replace method_fields macros in MCP binding

  method_fields only parses OpenAPI format (paths → operationId). The
  catalog MCP binding was the only caller passing an OpenRPC file, which
  uses a different structure (methods[].name). This produced a build
  failure: "Operation ID search_catalog not found".

  Replace with extension_schema_fields pointing at the underlying JSON
  schemas directly, matching the pattern used by cart-mcp.md.

* remove hard page-size cap; define clamp semantics

  Drop the maximum: 25 constraint on pagination.limit — businesses
  set their own upper bound. Add Page Size section to search.md
  defining the contract: limit is a request not a guarantee,
  implementations SHOULD accept at least 10, and MAY clamp silently
  when the request exceeds their maximum.

* update context signal guidance

* fix: replace enum with open string for `match`

  Strict enums in response schemas are a forward-compatibility trap: adding
  a new value breaks older clients whose validators reject unrecognized
  strings. match is server-controlled and will likely grow as resolution
  strategies evolve (e.g., fuzzy, semantic matching).

  Switch to open string with well-known values documented in description
  and examples array. Businesses can implement additional resolution
  strategies without requiring a schema version bump.

* feat: unit_price for per-unit pricing

  Optional per-unit price comparison for products sold by weight,
  volume, or length (e.g., "$1.33 per 100ml").

  Schema shape: amount + currency + measure + reference
  - measure: product quantity in packaging (e.g., 750ml bottle)
  - reference: display denominator (e.g., per 100ml, per 1kg)
  - amount: precomputed by business, not derived by platform

  Design decisions:
  - Business precomputes amount rather than platform deriving it
  - measure.value is number (fractional packaging, e.g., 0.75L),
    reference.value is integer (standard denominators are always
    whole: 1kg, 100ml, 1m)
  - Units are open vocabulary strings, because unit systems vary
    by product category

* feat: availability.status qualifies available state

  available (bool) answers "can I buy this?" — today this covers
  in-stock, backorder, and preorder without distinguishing between
  them. status (string) adds the missing fulfillment contract: why
  is it available, and when will it ship?

  status qualifies available, not the other way around. A producer
  that only sets available without status is still valid — status is
  additive context, not a replacement. Consumers that don't recognize
  a status value fall back to the boolean.

  Open vocabulary (not enum) so new values extend without schema changes

* feat: extensible search inputs

  Allow extensions to support image search, vector search, and other
  modalities without forcing validators to break when query is omitted.

  Remove required:["query"] from search_request. The base schema defines
  structural vocabulary; leaf schemas own behavioral contracts (e.g.,
  require query, or accept query-or-like via anyOf).

  Add Search Inputs section: requests MUST include at least one recognized
  input, implementations define and enforce their own presence/content
  rules.

* tighten pagination response constraints

  Make has_next_page required and enforce cursor presence via if/then
  when has_next_page is true. Keeps total_count and the pagination
  object itself optional.

* extract reusable amount type w/ ISO 4217 guidance

  Monetary amounts were defined inline across 8 schemas with
  inconsistent descriptions (e.g., "minor (cents) currency units")
  that baked in a two-decimal assumption. Not all currencies use
  cents — ISO 4217 defines per-currency exponents: 2 for USD, 0 for
  JPY, 3 for KWD.

  Extracts types/amount.json as the single source of truth for the
  integer minor-unit representation with exponent guidance. All amount
  fields now $ref the shared type with local descriptions using
  consistent "in ISO 4217 minor units" phrasing.

* barcode as typed array

  Replaces single `barcode` string with `barcodes` array of {type, value}
  objects supporting multiple barcode standards (UPC, EAN, ISBN, GTIN, JAN)

* fix: pluralize array field names for consistency

  Every array field in UCP schemas uses plural names (tags, variants,
  barcodes, options, links, etc.) except category and input which
  slipped through as singular. Inconsistent naming forces implementers
  to memorize exceptions rather than rely on convention.

  Rename category→categories (product, variant, search_filters) and
  input→inputs (variant). All are new in this PR with no external
  consumers; non-breaking.

* replace media enum with open string vocabulary

  Open string vocabulary preserves extensibility while the flat
  structure with additionalProperties lets vendors add type-specific
  fields without schema changes. Codified "prefer open vocabularies
  over enums" guidance in schema-authoring.md.

* align REST+MCP examples with schema definitions

  Capability envelope in response examples used array-of-objects format
  but the schema defines an object-keyed-by-name registry pattern.
  Context examples used shorthand field names (country, region) that
  don't match the schema (address_country, address_region).

* clarify filter-only browse as valid search input

  The search schema already accepts requests without query (no required
  array on search_request), but the normative language in search.md
  only listed query as a valid input, making filter-only browse
  technically non-conformant despite being listed as a core use case.

  Update Search Inputs section to explicitly list filters as a
  standalone input alongside query and extension-defined inputs.
  Filter-only requests represent browse operations where the business
  returns matching products without text-relevance ranking.

* enforce inputs:required on lookup variants

  The prose MUST ("variants without inputs MUST NOT appear in lookup
  responses") was not enforced by schema. The inputs field lived on
  the shared variant type as optional, leaking a lookup-specific
  concern into search responses where it has no meaning.

  Move inputs out of shared variant.json and into a lookup_variant
  composition in catalog_lookup.json. The lookup response overrides
  product.variants.items to use the lookup-specific shape. Shared
  variant type is now capability-agnostic.

* clarify independent adoption

  Search and lookup can be adopted independently.

* define price filter currency contract

  Define waterfall: filter is denominated in context.currency; when
  presentment differs, business SHOULD convert; if conversion not
  supported, MAY ignore and SHOULD notify via message; when
  context.currency absent, denomination is ambiguous. Platforms SHOULD
  include context.currency when sending price filters.
…l-Commerce-Protocol#243)

- Added MULTI_STATUS=false and ENABLE_GITHUB_PULL_REQUEST_SUMMARY_COMMENT=false to the Super-Linter environment block to prevent it from fatally crashing when the GITHUB_TOKEN is omitted.

Co-authored-by: Ilya Grigorik <ilya@grigorik.com>
…sal-Commerce-Protocol#248)

- Updated pyproject.toml to require mkdocs-material >=9.5.0 and regenerated uv.lock to resolve build errors related to the 'material.extensions.emoji' namespace which changed in version 9.4.
…-Protocol#226)

* docs: add a link checker to validate all links and URLs

* fix(docs): fix broken link anchors, paths, and absolute urls in code blocks on main

* docs: resolve remaining absolute URLs and broken anchors on main

* docs: parameterize hardcoded json versions in examples

* ci: fix target branch condition for release link check

- Explicitly targeted the 'Build and Verify Specification Docs' isolated build step to run on release branches only, rather than just any non-main branch.

* ci: fix link checker resolving absolute paths in flat builds

- Updated check_links.py to automatically strip version prefixes (e.g. 'latest', 'draft') from absolute URLs if the corresponding version directory doesn't exist locally, ensuring cross-version links are validated correctly against flat mkdocs builds during CI.

* ci: handle SITE_URL subpaths in link checker

- Updated check_links.py to correctly strip the configured SITE_URL subpath (e.g. '/ucp/') from the start of absolute links before resolving them against the local flat file structure, fixing false positive broken links when testing on forked repositories.

* ci: fix ruff formatting for check_links.py

* docs: natively generate checkout and error response anchors

- Fixed broken anchor links for '#checkout' and '#error-response' in the 'draft' branch's checkout documentation.
- Added explicit HTML anchor tags to the Capability Schema Definition heading.
- Rendered the 'error_response.json' schema dictionary natively at the bottom of checkout.md to ensure downstream schemas that reference it can link to it successfully.

* chore: fix openapi lint errors and schema versions

- Restored a placeholder 'draft' version exclusively to OpenAPI and OpenRPC definitions to satisfy strict Spectral validation rules, while leaving standard JSON schemas cleanly unversioned to be dynamically injected by the build pipeline.
- Abstracted duplicate absolute file references in 'handlers/tokenization/openapi.json' behind a local OpenAPI '#/components/schemas/' pointer.
- Fixed Super-Linter CI pipeline failing to pick up the '.spectral.yaml' overrides by explicitly declaring the 'OPENAPI_CONFIG_FILE' environment variable in the workflow configuration, ensuring Spectral correctly ignores the 'oas3-valid-media-example' bug.
…#246)

* feat(discount): extend discount capability to cart

  Discount previously only extended checkout, leaving cart without
  discount code support. This meant automatic discounts were invisible
  during cart exploration and agents couldn't show savings pre-checkout.

  - Add dev.ucp.shopping.cart extension in discount schema (create/update)
  - Update discovery example to show cart + checkout extends
  - Add cart-to-checkout continuity requirement (MUST carry forward codes)
  - Add cart discount example to specification docs

* convert examples to Material content tabs

  Replace **Request:**/**Response:** markdown with === "Request"/=== "Response"
  tabbed format for consistency with cart-rest.md and checkout-rest.md.

* clarify cart-to-checkout discount behavior

Applied codes should be carried over and (re)evaluated at checkout
time to preserve buyer context and continuity.
…ce-Protocol#260)

Extract reverse_domain_name from ucp.json $defs into
  shopping/types/reverse_domain_name.json. Update all propertyNames
  refs in ucp.json to point to the standalone file directly.

  The docs renderer generates page-local anchors for $defs entries
  (prefixed with the parent schema name), but standalone types get
  rendered on the reference page with a linkable section. The $defs
  indirection caused broken #ucp-reverse-domain-name anchors on
  checkout, cart, catalog, and reference pages.
…tocol#262)

Bumps [black](https://github.com/psf/black) from 26.1.0 to 26.3.1.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](psf/black@26.1.0...26.3.1)

---
updated-dependencies:
- dependency-name: black
  dependency-version: 26.3.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…#203)

* remove stale risk_signals references from spec

  Clean up all risk_signals language from the earlier draft that was
  superseded by the signals proposal (issue Universal-Commerce-Protocol#153).

* feat: signals for authorization & abuse framework

  Platforms mediate buyer interaction, making them the sole party
  able to observe the transaction environment (IP, user agent, etc).
  Businesses need this data for authorization decisions, rate limiting,
  and abuse prevention — but have no direct access to it.

  - Signals are platform attestations: values MUST reflect direct
    observations, not relayed buyer claims
  - Signals serve authorization, rate limiting, and abuse prevention
    across cart and checkout
  - Signal feedback via info messages with well-known codes: risk, abuse
  - Proprietary signals can use reverse-domain naming (com.example.score)

* enforce reverse-DNS on signal keys via propertyNames

  Unlike other UCP extension points — capability registries key by
  reverse-domain name, arrays keep items independent — signals is a shared
  flat map where multiple independent extensions contribute keys into the
  same object. Without naming discipline, collisions are inevitable: two
  extensions both defining "device_id" or "ja4" with different types or
  semantics produces silently unsatisfiable allOf composition.

  Reverse-domain naming makes every key self-describing: you know who
  defined it, what schema governs it, and collisions are structurally
  impossible. This is the same coordination-free mechanism UCP already
  uses for capabilities, services, and payment handlers.

  This commit enforces the MUST at the schema level via propertyNames
  (not just prose), renames well-known signals into the dev.ucp namespace
  (buyer_ip → dev.ucp.buyer_ip, user_agent → dev.ucp.user_agent), and
  updates all docs and examples to match.

* broaden signals to include 3P attestations

  The original spec language required signals to be "direct platform
  observations," which excluded a valuable class of signals: independently
  verifiable third-party attestations (e.g., cryptographically signed
  results from an external verifier). These aren't buyer-asserted claims —
  the business can validate the signature against the provider's published
  key set without trusting the platform at all.

* fix attestation example: self-verifiable shape

  Updated shape includes kid (RFC 7517 key selection/rotation), the
  complete signed payload (opaque, extension-defined), and sig over that
  payload. The business can now verify end-to-end: fetch JWKS, select
  key by kid, verify sig over payload.

* simplify: single code, relax info-only

  Replace the risk/abuse code taxonomy with a single `signal` code.
  The path field already identifies what's being requested, and the
  signal is self-describing.

  Removes info-only language to allow and support different business
  strategies for information vs required signals.

* echo back signals and context in responses

  Remove ucp_response: "omit" from signals (checkout, cart) and context
  (checkout). The business now echoes these back like any other field.
  The only remaining response-omit in shopping schemas is
  token_credential.token, which is correctly omit'd (sensitive payment
  data that must not leak).

* extend signals to catalog

  Add signals to catalog search and lookup requests.

  Drop sibling description overrides from all $ref sites (checkout, cart,
  catalog_search, catalog_lookup) — signals.json owns its description,
  consumers just $ref it. Also drop "Transaction" from the description
  since signals now span non-transactional operations (catalog search).

* deprecate risk_signals with transition annotation

  The old risk_signals field was removed outright in an earlier commit,
  this adds risk_signals back to checkout schema with deprecated: true
  and a transition from optional to omit on the complete operation,
  signaling removal in the next version.
…Protocol#250)

* feat: eligibility claims & verification contract

  Introduce `context.eligibility` — buyer claims about eligible benefits
  (loyalty membership, payment instrument perks, etc.) that Businesses
  can act on across the shopping lifecycle.

  Processing model:
  - Platform provides claims via context.eligibility on any request
  - Business MAY act on recognized claims (adjust pricing, product
    access, provisional discounts); MUST ignore unrecognized claims
  - At checkout completion, all claims that influenced the checkout
    MUST be resolved: verified against proof, or rescinded by Platform
  - Unresolved claims block completion (invalid_eligibility error)
  - Business MUST NOT mutate checkout on verification failure

  Layering:
  - context.json: eligibility array with reverse-domain $ref validation
  - checkout.md: normative verification contract (core obligation)
  - discount.json: provisional + eligibility fields on applied_discount
    for structured attribution when discount extension is active
  - catalog/index.md: MAY adjust price/list_price for eligible claims
  - error_code.json: invalid_eligibility standard error

  Key design decision: eligibility lives on context (not PaymentInstrument)
  enabling full-funnel coverage from catalog through checkout. Verification
  is a core checkout concern; the discount extension adds attribution.

* align error code to noun_adjective pattern

* fix: address PR Universal-Commerce-Protocol#250 review feedback

  Terminology:
  - "claims that influenced the checkout" → "accepted claims" throughout,
    aligning with the accepted/not-accepted partition (maximenajim, ACSchil)
  - "not applied" → "not accepted" for consistency

  Verification semantics:
  - Clarify that verification failure MUST only affect the messages array,
    not checkout state (line items, totals, discounts, etc.) (maximenajim)
  - Add path field to eligibility_invalid example for machine-readable
    partial failure identification (ACSchil)

  Messages contract:
  - Add MAY use type: "info" to explain effects of accepted claims,
    complementing SHOULD warn on rejection (maximenajim)

  Catalog:
  - Add non-binding pricing contract to "Relationship to Checkout" section:
    catalog responses are not transactional commitments, checkout is
    authoritative, responses SHOULD NOT be reused across sessions
    without re-validation (maximenajim)

* add eligibility message codes for warning and info

  Standardize codes for the three eligibility message types:
  eligibility_not_accepted (warning), eligibility_accepted (info),
  and eligibility_invalid (error at completion).

* remove display MUST from provisional

  Per TC discussion, this is already captured via messages flow.

---------

Co-authored-by: Alex Schillinger <alexcschillinger@gmail.com>
ucp.dev site building cleanup:

- Stop setting latest alias in build_local.sh script, as it was updating latest alias to draft
- Add a symlink for /specifications/ to /latest/specifications/
- Update pages build for main to delete old files, and preserve version dirs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants