From 86c7805d36adc2fe574dbf6aecb5d697151faca3 Mon Sep 17 00:00:00 2001 From: Westin Wrzesinski Date: Mon, 25 May 2026 12:09:02 -0500 Subject: [PATCH] chore(protocol): refresh UCP snapshot Update vendored UCP schemas and services from upstream commit e627e4c9c84b5a9b54fa19c7e0c2231e78c00d8e. --- protocol/schemas/common/identity_linking.json | 66 +++++ protocol/schemas/common/loyalty.json | 272 ++++++++++++++++++ .../{shopping => common}/types/amount.json | 2 +- .../schemas/common/types/description.json | 22 ++ .../types/error_code.json | 8 +- .../types/error_response.json | 2 +- protocol/schemas/common/types/info_code.json | 13 + .../{shopping => common}/types/link.json | 2 +- protocol/schemas/common/types/media.json | 36 +++ .../{shopping => common}/types/message.json | 2 +- .../types/message_error.json | 2 +- .../types/message_info.json | 5 +- .../types/message_warning.json | 5 +- protocol/schemas/common/types/pagination.json | 52 ++++ .../types/postal_address.json | 2 +- protocol/schemas/common/types/price.json | 22 ++ .../types/reverse_domain_name.json | 2 +- .../types/signed_amount.json | 2 +- .../schemas/common/types/warning_code.json | 13 + protocol/schemas/profile.json | 105 +++++++ protocol/schemas/service.json | 9 +- protocol/schemas/shopping/cart.json | 11 +- protocol/schemas/shopping/catalog_lookup.json | 177 ++++++++++++ protocol/schemas/shopping/catalog_search.json | 63 ++++ protocol/schemas/shopping/checkout.json | 8 +- protocol/schemas/shopping/discount.json | 10 +- protocol/schemas/shopping/fulfillment.json | 2 +- protocol/schemas/shopping/order.json | 7 +- protocol/schemas/shopping/split_payments.json | 81 ++++++ .../schemas/shopping/types/attribution.json | 11 + .../types/business_split_payments_config.json | 23 ++ protocol/schemas/shopping/types/category.json | 18 ++ protocol/schemas/shopping/types/context.json | 2 +- .../shopping/types/detail_option_value.json | 18 ++ .../schemas/shopping/types/expectation.json | 2 +- .../shopping/types/input_correlation.json | 19 ++ .../shopping/types/instrument_group.json | 30 ++ protocol/schemas/shopping/types/item.json | 2 +- .../schemas/shopping/types/option_value.json | 20 ++ .../shopping/types/payment_instrument.json | 2 +- .../schemas/shopping/types/price_filter.json | 17 ++ .../schemas/shopping/types/price_range.json | 21 ++ protocol/schemas/shopping/types/product.json | 89 ++++++ .../shopping/types/product_option.json | 25 ++ protocol/schemas/shopping/types/rating.json | 34 +++ .../shopping/types/retail_location.json | 2 +- .../shopping/types/search_filters.json | 18 ++ .../shopping/types/selected_option.json | 25 ++ .../shopping/types/shipping_destination.json | 2 +- protocol/schemas/shopping/types/total.json | 2 +- protocol/schemas/shopping/types/totals.json | 2 +- protocol/schemas/shopping/types/variant.json | 167 +++++++++++ protocol/schemas/transports/a2a_message.json | 113 ++++++++ .../schemas/transports/embedded_message.json | 55 ++++ protocol/schemas/transports/jsonrpc.json | 98 +++++++ .../schemas/transports/mcp_tool_call.json | 108 +++++++ protocol/schemas/ucp.json | 8 +- .../services/shopping/embedded.openrpc.json | 18 +- protocol/source-lock.json | 192 ++++++++++--- 59 files changed, 2058 insertions(+), 88 deletions(-) create mode 100644 protocol/schemas/common/identity_linking.json create mode 100644 protocol/schemas/common/loyalty.json rename protocol/schemas/{shopping => common}/types/amount.json (83%) create mode 100644 protocol/schemas/common/types/description.json rename protocol/schemas/{shopping => common}/types/error_code.json (52%) rename protocol/schemas/{shopping => common}/types/error_response.json (92%) create mode 100644 protocol/schemas/common/types/info_code.json rename protocol/schemas/{shopping => common}/types/link.json (92%) create mode 100644 protocol/schemas/common/types/media.json rename protocol/schemas/{shopping => common}/types/message.json (84%) rename protocol/schemas/{shopping => common}/types/message_error.json (95%) rename protocol/schemas/{shopping => common}/types/message_info.json (82%) rename protocol/schemas/{shopping => common}/types/message_warning.json (85%) create mode 100644 protocol/schemas/common/types/pagination.json rename protocol/schemas/{shopping => common}/types/postal_address.json (96%) create mode 100644 protocol/schemas/common/types/price.json rename protocol/schemas/{shopping => common}/types/reverse_domain_name.json (85%) rename protocol/schemas/{shopping => common}/types/signed_amount.json (85%) create mode 100644 protocol/schemas/common/types/warning_code.json create mode 100644 protocol/schemas/profile.json create mode 100644 protocol/schemas/shopping/catalog_lookup.json create mode 100644 protocol/schemas/shopping/catalog_search.json create mode 100644 protocol/schemas/shopping/split_payments.json create mode 100644 protocol/schemas/shopping/types/attribution.json create mode 100644 protocol/schemas/shopping/types/business_split_payments_config.json create mode 100644 protocol/schemas/shopping/types/category.json create mode 100644 protocol/schemas/shopping/types/detail_option_value.json create mode 100644 protocol/schemas/shopping/types/input_correlation.json create mode 100644 protocol/schemas/shopping/types/instrument_group.json create mode 100644 protocol/schemas/shopping/types/option_value.json create mode 100644 protocol/schemas/shopping/types/price_filter.json create mode 100644 protocol/schemas/shopping/types/price_range.json create mode 100644 protocol/schemas/shopping/types/product.json create mode 100644 protocol/schemas/shopping/types/product_option.json create mode 100644 protocol/schemas/shopping/types/rating.json create mode 100644 protocol/schemas/shopping/types/search_filters.json create mode 100644 protocol/schemas/shopping/types/selected_option.json create mode 100644 protocol/schemas/shopping/types/variant.json create mode 100644 protocol/schemas/transports/a2a_message.json create mode 100644 protocol/schemas/transports/embedded_message.json create mode 100644 protocol/schemas/transports/jsonrpc.json create mode 100644 protocol/schemas/transports/mcp_tool_call.json diff --git a/protocol/schemas/common/identity_linking.json b/protocol/schemas/common/identity_linking.json new file mode 100644 index 00000000..170c59b4 --- /dev/null +++ b/protocol/schemas/common/identity_linking.json @@ -0,0 +1,66 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/common/identity_linking.json", + "name": "dev.ucp.common.identity_linking", + "title": "Identity Linking", + "description": "Capability schema for identity linking. Businesses declare the user-authenticated scopes they offer in a flat 'scopes' map. Each key is the OAuth scope string as it appears on the wire ('{capability}:{scope}', e.g. 'dev.ucp.shopping.order:read'). Scope presence implies that the corresponding operations require user authentication. Operations not gated by any listed scope operate at whatever access level the business permits; UCP does not prescribe a default.", + "$comment": "Auth mechanism: business-hosted OAuth 2.0 with RFC 8414 discovery. The 'config' object is open for non-breaking extension. Reserved extension point: 'providers' (map of trusted identity providers keyed by reverse-domain, with a 'type' discriminator defaulting to 'oauth2' — enabling delegated IdP, identity chaining, and future non-OAuth mechanisms such as wallet attestation). When 'providers' is absent, platforms MUST use OAuth 2.0 with RFC 8414 discovery on the business domain. Platforms MUST ignore unrecognized fields in 'config' and fall back to this default behavior.", + + "$defs": { + "scope_policy": { + "type": "object", + "title": "Scope Policy", + "description": "Per-scope policy and metadata — auth constraints (e.g. min_acr, max_token_age), declarative metadata (e.g. claims produced, consent descriptions), or any other scope-specific configuration. An empty object means user authentication is required with no additional policy. Open for non-breaking extension.", + "properties": { + "description": { + "$ref": "types/description.json", + "description": "Optional human-readable description of the scope that platforms can use to present and explain context (requirement and value) to the user." + } + }, + "additionalProperties": true + }, + + "scope_token": { + "type": "string", + "description": "OAuth scope string formed by joining a capability name and a scope name with a colon: '{capability}:{scope}', e.g. 'dev.ucp.shopping.order:read'. Capability names use reverse-DNS naming; scope names denote the permission granted, defined by each capability's spec (e.g. 'read', 'manage', 'create'). Platforms request these strings verbatim in OAuth 'scope' parameters; issued tokens carry them in the 'scope' claim.", + "pattern": "^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+:[a-z][a-z0-9_]*$" + }, + + "dev.ucp.common.identity_linking": { + "platform_schema": { + "title": "Identity Linking (Platform)", + "description": "Platform-level identity linking capability declaration. Platforms advertise support for identity linking; no auth-specific config is required.", + "allOf": [ + { "$ref": "../capability.json#/$defs/platform_schema" } + ] + }, + "business_schema": { + "title": "Identity Linking (Business)", + "description": "Business-level identity linking configuration. Businesses declare the user-authenticated scopes they offer in 'config.scopes'.", + "allOf": [ + { "$ref": "../capability.json#/$defs/business_schema" }, + { + "type": "object", + "required": ["config"], + "properties": { + "config": { + "type": "object", + "$comment": "Reserved extension point: 'providers' (map of trusted identity providers keyed by reverse-domain). See schema-level $comment for details.", + "required": ["scopes"], + "properties": { + "scopes": { + "type": "object", + "description": "Map of user-authenticated scopes offered by this business. Each key is an OAuth scope string formed as '{capability}:{scope}' (e.g. 'dev.ucp.shopping.order:read'). Scope presence in this map declares that the corresponding operations require a user identity token. Operations not gated by any listed scope operate at whatever access level the business permits; UCP does not prescribe a default. Each value is a per-scope policy object (empty object means user auth required with no additional policy).", + "propertyNames": { "$ref": "#/$defs/scope_token" }, + "additionalProperties": { "$ref": "#/$defs/scope_policy" } + } + }, + "additionalProperties": true + } + } + } + ] + } + } + } +} diff --git a/protocol/schemas/common/loyalty.json b/protocol/schemas/common/loyalty.json new file mode 100644 index 00000000..3eb8dd9c --- /dev/null +++ b/protocol/schemas/common/loyalty.json @@ -0,0 +1,272 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/common/loyalty.json", + "name": "dev.ucp.common.loyalty", + "title": "Loyalty Extension", + "description": "Extends various Capabilities with loyalty support using memberships info.", + "$defs": { + "reward_amount": { + "type": "integer", + "minimum": 0, + "description": "Non-negative integer amount denominated in the minor unit of the associated reward currency. The associated reward currency's `decimal_places` defines the minor-to-major ratio and defaults to 0 when omitted." + }, + "earning_breakdown": { + "type": "object", + "description": "Breakdown rule of the reward earnings", + "required": ["id", "amount", "description"], + "properties": { + "id": { + "type": "string", + "description": "Unique rewards breakdown rule identifier." + }, + "amount": { + "$ref": "#/$defs/reward_amount", + "description": "Rewards earned from this rule." + }, + "description": { + "type": "string", + "description": "A display-ready, human-readable rationale for the specific rewards (e.g. 2x on footwear)." + }, + "benefit_id": { + "type": "string", + "description": "Optional `id` of the membership_tier_benefit that produced this rewards rule. Resolves against `membership_tier_benefit.id` within the same parent loyalty membership." + } + } + }, + "earning_forecast": { + "type": "object", + "description": "Preview of rewards to be earned from the current transaction.", + "required": ["amount"], + "properties": { + "amount": { + "$ref": "#/$defs/reward_amount", + "description": "Total rewards to be earned if the transaction completes." + }, + "breakdown": { + "type": "array", + "items": { + "$ref": "#/$defs/earning_breakdown" + }, + "description": "List of breakdown of earning contributing to the total." + } + } + }, + "reward_currency": { + "type": "object", + "description": "The currency of the loyalty reward.", + "required": ["name", "code"], + "properties": { + "name": { + "type": "string", + "description": "Human-readable name of the currency (e.g. 'LoyaltyStars')." + }, + "code": { + "type": "string", + "description": "Business-specific representation of the currency (e.g. 'LST')." + }, + "decimal_places": { + "type": "integer", + "minimum": 0, + "default": 0, + "description": "The position of a digit to the right of a decimal point. Applies to all amount related fields for rewards." + } + } + }, + "membership_reward": { + "type": "object", + "description": "Quantifiable reward type and optional earning forecast for the current transaction.", + "required": ["currency"], + "properties": { + "currency": { + "type": "object", + "$ref": "#/$defs/reward_currency", + "description": "A unit of value that customers can accumulate through various commercial activities." + }, + "earning_forecast": { + "type": "object", + "$ref": "#/$defs/earning_forecast", + "description": "Preview of rewards to be earned from the current transaction." + } + } + }, + "membership_tier_benefit": { + "type": "object", + "description": "Benefits associated with a membership tier.", + "required": ["id", "description"], + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the tier benefit." + }, + "description": { + "type": "string", + "description": "A display-ready, human-readable explanation of this benefit (e.g. 'Early access to sales')." + } + } + }, + "membership_tier": { + "type": "object", + "description": "Specific achievement rank or status milestone that unlocks escalating value as a member progresses through activity or spend.", + "required": ["id", "name"], + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the membership tier." + }, + "name": { + "type": "string", + "description": "The human-readable name of the tier (e.g., 'Platinum')." + }, + "benefits": { + "type": "array", + "items": { + "$ref": "#/$defs/membership_tier_benefit" + }, + "description": "List of benefits associated with this tier." + } + } + }, + "loyalty_membership": { + "type": "object", + "description": "Loyalty membership the business has accepted for the eligibility claim represented by the parent map key. Programs that can be joined independently MUST be modeled as separate sibling entries under the loyalty map, distinguished by reverse-domain naming (e.g., 'com.example.rewards' and 'com.example.rewards.card').", + "required": ["id", "name", "provisional"], + "properties": { + "id": { + "type": "string", + "description": "Unique loyalty membership identifier." + }, + "name": { + "type": "string", + "description": "Business specific name of the loyalty membership/program." + }, + "display_id": { + "type": "string", + "description": "A masked or partial version of the membership id for user recognition (e.g., '****5678'). MUST NOT be set if the membership has not been verified." + }, + "tiers": { + "type": "array", + "items": { + "$ref": "#/$defs/membership_tier" + }, + "description": "Active or display-safe tier context for this membership. Most programs are single-status (one entry); programs with parallel status dimensions (e.g., current and lifetime) populate one entry per active tier. Omitted when no tier context has been resolved." + }, + "rewards": { + "type": "array", + "items": { + "$ref": "#/$defs/membership_reward" + }, + "description": "Reward types and earning forecasts associated with this membership. Each object encapsulates one type of reward." + }, + "provisional": { + "type": "boolean", + "description": "True if this membership requires additional verification." + } + } + }, + "loyalty": { + "type": "object", + "description": "Key-value map whose keys represent buyer/platform asserted eligibility claims and whose values represent associated membership information. All loyalty keys MUST use reverse-domain naming to ensure provenance and prevent collisions when multiple extensions contribute to the shared namespace.", + "propertyNames": { + "$ref": "types/reverse_domain_name.json", + "description": "Reverse-domain identifier that represents eligibility claim accepted by the Business for this membership." + }, + "additionalProperties": { + "$ref": "#/$defs/loyalty_membership", + "description": "Payload that describes the membership information corresponding to the claim.", + "ucp_request": "omit" + } + }, + "dev.ucp.shopping.catalog.search": { + "title": "Catalog Search with Loyalty", + "description": "Catalog Search response extended with Loyalty capability.", + "allOf": [ + { + "$ref": "../shopping/catalog_search.json#/$defs/search_response" + }, + { + "type": "object", + "properties": { + "loyalty": { + "$ref": "#/$defs/loyalty", + "ucp_request": "omit" + } + } + } + ] + }, + "dev.ucp.shopping.catalog.lookup": { + "title": "Catalog Lookup with Loyalty", + "description": "Catalog Lookup response extended with Loyalty capability.", + "oneOf": [ + { + "allOf": [ + { + "$ref": "../shopping/catalog_lookup.json#/$defs/lookup_response" + }, + { + "type": "object", + "properties": { + "loyalty": { + "$ref": "#/$defs/loyalty", + "ucp_request": "omit" + } + } + } + ] + }, + { + "allOf": [ + { + "$ref": "../shopping/catalog_lookup.json#/$defs/get_product_response" + }, + { + "type": "object", + "properties": { + "loyalty": { + "$ref": "#/$defs/loyalty", + "ucp_request": "omit" + } + } + } + ] + } + ] + }, + "dev.ucp.shopping.cart": { + "title": "Cart with Loyalty", + "description": "Cart extended with Loyalty capability.", + "allOf": [ + { + "$ref": "../shopping/cart.json" + }, + { + "type": "object", + "properties": { + "loyalty": { + "$ref": "#/$defs/loyalty", + "ucp_request": "omit" + } + } + } + ] + }, + "dev.ucp.shopping.checkout": { + "title": "Checkout with Loyalty", + "description": "Checkout extended with Loyalty capability.", + "allOf": [ + { + "$ref": "../shopping/checkout.json" + }, + { + "type": "object", + "properties": { + "loyalty": { + "$ref": "#/$defs/loyalty", + "ucp_request": "omit" + } + } + } + ] + } + } +} diff --git a/protocol/schemas/shopping/types/amount.json b/protocol/schemas/common/types/amount.json similarity index 83% rename from protocol/schemas/shopping/types/amount.json rename to protocol/schemas/common/types/amount.json index 3464f3f2..08e73b1b 100644 --- a/protocol/schemas/shopping/types/amount.json +++ b/protocol/schemas/common/types/amount.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://ucp.dev/schemas/shopping/types/amount.json", + "$id": "https://ucp.dev/schemas/common/types/amount.json", "title": "Amount", "description": "Monetary amount in the currency's minor unit as defined by ISO 4217. Refer to the currency's exponent to determine minor-to-major ratio (e.g., 2 for USD, 0 for JPY, 3 for KWD).", "type": "integer", diff --git a/protocol/schemas/common/types/description.json b/protocol/schemas/common/types/description.json new file mode 100644 index 00000000..d3d9de33 --- /dev/null +++ b/protocol/schemas/common/types/description.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/common/types/description.json", + "title": "Description", + "description": "Description content in one or more formats. At least one format must be provided.", + "type": "object", + "properties": { + "plain": { + "type": "string", + "description": "Plain text content." + }, + "html": { + "type": "string", + "description": "HTML-formatted content. Security: Platforms MUST sanitize before rendering—strip scripts, event handlers, and untrusted elements. Treat all rich text as untrusted input." + }, + "markdown": { + "type": "string", + "description": "Markdown-formatted content." + } + }, + "minProperties": 1 +} diff --git a/protocol/schemas/shopping/types/error_code.json b/protocol/schemas/common/types/error_code.json similarity index 52% rename from protocol/schemas/shopping/types/error_code.json rename to protocol/schemas/common/types/error_code.json index 9f330419..1ccffbe8 100644 --- a/protocol/schemas/shopping/types/error_code.json +++ b/protocol/schemas/common/types/error_code.json @@ -1,8 +1,8 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://ucp.dev/schemas/shopping/types/error_code.json", + "$id": "https://ucp.dev/schemas/common/types/error_code.json", "title": "Error Code", - "description": "Error code identifying the type of error. Standard errors are defined in specification (see examples), and have standardized semantics; freeform codes are permitted.", + "description": "Error code identifying the type of error. Standard errors are defined in capability specifications (see examples) and have standardized semantics; freeform codes are permitted.", "type": "string", "examples": [ "not_found", @@ -10,6 +10,8 @@ "item_unavailable", "address_undeliverable", "payment_failed", - "eligibility_invalid" + "eligibility_invalid", + "identity_required", + "insufficient_scope" ] } diff --git a/protocol/schemas/shopping/types/error_response.json b/protocol/schemas/common/types/error_response.json similarity index 92% rename from protocol/schemas/shopping/types/error_response.json rename to protocol/schemas/common/types/error_response.json index fb5ac3d7..0bc9aa70 100644 --- a/protocol/schemas/shopping/types/error_response.json +++ b/protocol/schemas/common/types/error_response.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://ucp.dev/schemas/shopping/types/error_response.json", + "$id": "https://ucp.dev/schemas/common/types/error_response.json", "title": "Error Response", "description": "Generic error response when business logic prevents resource creation or failed to retrieve resource. Used when no valid resource can be established.", "type": "object", diff --git a/protocol/schemas/common/types/info_code.json b/protocol/schemas/common/types/info_code.json new file mode 100644 index 00000000..a3547fc0 --- /dev/null +++ b/protocol/schemas/common/types/info_code.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/common/types/info_code.json", + "title": "Info Code", + "description": "Info code identifying the type of informational message. Standard codes are defined in capability specifications (see examples) and have standardized semantics; freeform codes are permitted.", + "type": "string", + "examples": [ + "identity_optional", + "signal", + "free_shipping", + "not_found" + ] +} diff --git a/protocol/schemas/shopping/types/link.json b/protocol/schemas/common/types/link.json similarity index 92% rename from protocol/schemas/shopping/types/link.json rename to protocol/schemas/common/types/link.json index b377b4a0..2a952fff 100644 --- a/protocol/schemas/shopping/types/link.json +++ b/protocol/schemas/common/types/link.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://ucp.dev/schemas/shopping/types/link.json", + "$id": "https://ucp.dev/schemas/common/types/link.json", "title": "Link", "type": "object", "required": [ diff --git a/protocol/schemas/common/types/media.json b/protocol/schemas/common/types/media.json new file mode 100644 index 00000000..57e28cfb --- /dev/null +++ b/protocol/schemas/common/types/media.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/common/types/media.json", + "title": "Media", + "description": "Media item (image, video, etc.).", + "type": "object", + "required": [ + "type", + "url" + ], + "properties": { + "type": { + "type": "string", + "description": "Media type. Well-known values: `image`, `video`, `model_3d`." + }, + "url": { + "type": "string", + "format": "uri", + "description": "URL to the media resource." + }, + "alt_text": { + "type": "string", + "description": "Accessibility text describing the media." + }, + "width": { + "type": "integer", + "minimum": 1, + "description": "Width in pixels (for images/video)." + }, + "height": { + "type": "integer", + "minimum": 1, + "description": "Height in pixels (for images/video)." + } + } +} diff --git a/protocol/schemas/shopping/types/message.json b/protocol/schemas/common/types/message.json similarity index 84% rename from protocol/schemas/shopping/types/message.json rename to protocol/schemas/common/types/message.json index c0bbbe90..1c317275 100644 --- a/protocol/schemas/shopping/types/message.json +++ b/protocol/schemas/common/types/message.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://ucp.dev/schemas/shopping/types/message.json", + "$id": "https://ucp.dev/schemas/common/types/message.json", "title": "Message", "type": "object", "description": "Container for error, warning, or info messages.", diff --git a/protocol/schemas/shopping/types/message_error.json b/protocol/schemas/common/types/message_error.json similarity index 95% rename from protocol/schemas/shopping/types/message_error.json rename to protocol/schemas/common/types/message_error.json index 2b157a98..35533ce2 100644 --- a/protocol/schemas/shopping/types/message_error.json +++ b/protocol/schemas/common/types/message_error.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://ucp.dev/schemas/shopping/types/message_error.json", + "$id": "https://ucp.dev/schemas/common/types/message_error.json", "title": "Message Error", "type": "object", "required": [ diff --git a/protocol/schemas/shopping/types/message_info.json b/protocol/schemas/common/types/message_info.json similarity index 82% rename from protocol/schemas/shopping/types/message_info.json rename to protocol/schemas/common/types/message_info.json index 4960378b..74ee2986 100644 --- a/protocol/schemas/shopping/types/message_info.json +++ b/protocol/schemas/common/types/message_info.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://ucp.dev/schemas/shopping/types/message_info.json", + "$id": "https://ucp.dev/schemas/common/types/message_info.json", "title": "Message Info", "type": "object", "required": [ @@ -18,8 +18,7 @@ "description": "RFC 9535 JSONPath to the component the message refers to." }, "code": { - "type": "string", - "description": "Info code for programmatic handling." + "$ref": "info_code.json" }, "content_type": { "type": "string", diff --git a/protocol/schemas/shopping/types/message_warning.json b/protocol/schemas/common/types/message_warning.json similarity index 85% rename from protocol/schemas/shopping/types/message_warning.json rename to protocol/schemas/common/types/message_warning.json index d42210bb..a8740074 100644 --- a/protocol/schemas/shopping/types/message_warning.json +++ b/protocol/schemas/common/types/message_warning.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://ucp.dev/schemas/shopping/types/message_warning.json", + "$id": "https://ucp.dev/schemas/common/types/message_warning.json", "title": "Message Warning", "type": "object", "required": [ @@ -19,8 +19,7 @@ "description": "JSONPath (RFC 9535) to related field (e.g., $.line_items[0])." }, "code": { - "type": "string", - "description": "Warning code. Machine-readable identifier for the warning type (e.g., final_sale, prop65, fulfillment_changed, age_restricted, etc.)." + "$ref": "warning_code.json" }, "content": { "type": "string", diff --git a/protocol/schemas/common/types/pagination.json b/protocol/schemas/common/types/pagination.json new file mode 100644 index 00000000..a2508088 --- /dev/null +++ b/protocol/schemas/common/types/pagination.json @@ -0,0 +1,52 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/common/types/pagination.json", + "title": "Pagination", + "description": "Cursor-based pagination for list operations.", + "type": "object", + "$defs": { + "request": { + "type": "object", + "description": "Pagination parameters for requests.", + "properties": { + "cursor": { + "type": "string", + "description": "Opaque cursor from previous response." + }, + "limit": { + "type": "integer", + "minimum": 1, + "default": 10, + "description": "Requested page size. Implementations MAY clamp to a lower maximum." + } + } + }, + "response": { + "type": "object", + "description": "Pagination information in responses.", + "properties": { + "cursor": { + "type": "string", + "description": "Cursor to fetch the next page of results. MUST be present when has_next_page is true." + }, + "has_next_page": { + "type": "boolean", + "description": "Whether more results are available." + }, + "total_count": { + "type": "integer", + "minimum": 0, + "description": "Total number of matching items, if available." + } + }, + "required": ["has_next_page"], + "if": { + "properties": { "has_next_page": { "const": true } }, + "required": ["has_next_page"] + }, + "then": { + "required": ["cursor"] + } + } + } +} diff --git a/protocol/schemas/shopping/types/postal_address.json b/protocol/schemas/common/types/postal_address.json similarity index 96% rename from protocol/schemas/shopping/types/postal_address.json rename to protocol/schemas/common/types/postal_address.json index 3cf69437..a4fe35ee 100644 --- a/protocol/schemas/shopping/types/postal_address.json +++ b/protocol/schemas/common/types/postal_address.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://ucp.dev/schemas/shopping/types/postal_address.json", + "$id": "https://ucp.dev/schemas/common/types/postal_address.json", "title": "Postal Address", "type": "object", "properties": { diff --git a/protocol/schemas/common/types/price.json b/protocol/schemas/common/types/price.json new file mode 100644 index 00000000..d7b67078 --- /dev/null +++ b/protocol/schemas/common/types/price.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/common/types/price.json", + "title": "Price", + "description": "Price with explicit currency.", + "type": "object", + "required": [ + "amount", + "currency" + ], + "properties": { + "amount": { + "$ref": "amount.json", + "description": "Amount in ISO 4217 minor units. Use 0 for free items." + }, + "currency": { + "type": "string", + "description": "ISO 4217 currency code (e.g., 'USD', 'EUR', 'GBP').", + "pattern": "^[A-Z]{3}$" + } + } +} diff --git a/protocol/schemas/shopping/types/reverse_domain_name.json b/protocol/schemas/common/types/reverse_domain_name.json similarity index 85% rename from protocol/schemas/shopping/types/reverse_domain_name.json rename to protocol/schemas/common/types/reverse_domain_name.json index 6ecbfd9d..e328935b 100644 --- a/protocol/schemas/shopping/types/reverse_domain_name.json +++ b/protocol/schemas/common/types/reverse_domain_name.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://ucp.dev/schemas/shopping/types/reverse_domain_name.json", + "$id": "https://ucp.dev/schemas/common/types/reverse_domain_name.json", "title": "Reverse Domain Name", "description": "Reverse-domain identifier used for collision-safe namespacing of capabilities, services, handlers, eligibility claims, and extension-contributed keys. Must contain at least two dot-separated segments (e.g., 'dev.ucp.shopping.checkout', 'com.example.loyalty_gold').", "type": "string", diff --git a/protocol/schemas/shopping/types/signed_amount.json b/protocol/schemas/common/types/signed_amount.json similarity index 85% rename from protocol/schemas/shopping/types/signed_amount.json rename to protocol/schemas/common/types/signed_amount.json index b68d5778..fc0d6491 100644 --- a/protocol/schemas/shopping/types/signed_amount.json +++ b/protocol/schemas/common/types/signed_amount.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://ucp.dev/schemas/shopping/types/signed_amount.json", + "$id": "https://ucp.dev/schemas/common/types/signed_amount.json", "title": "Signed Amount", "description": "Monetary amount in the currency's minor unit as defined by ISO 4217. Refer to the currency's exponent to determine minor-to-major ratio (e.g., 2 for USD, 0 for JPY, 3 for KWD). May be negative — the sign is intrinsic to the value (e.g., discounts are negative, charges are positive).", "type": "integer" diff --git a/protocol/schemas/common/types/warning_code.json b/protocol/schemas/common/types/warning_code.json new file mode 100644 index 00000000..4cf54403 --- /dev/null +++ b/protocol/schemas/common/types/warning_code.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/common/types/warning_code.json", + "title": "Warning Code", + "description": "Warning code identifying the type of warning. Standard codes are defined in capability specifications (see examples) and have standardized semantics; freeform codes are permitted.", + "type": "string", + "examples": [ + "final_sale", + "prop65", + "fulfillment_changed", + "age_restricted" + ] +} diff --git a/protocol/schemas/profile.json b/protocol/schemas/profile.json new file mode 100644 index 00000000..04099459 --- /dev/null +++ b/protocol/schemas/profile.json @@ -0,0 +1,105 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/profile.json", + "title": "UCP Profile Document", + "description": "Variant-neutral wrapper schema for UCP profile documents. Use the business_schema definition to validate business profiles and the platform_schema definition to validate platform profiles.", + "allOf": [{ "$ref": "#/$defs/base" }], + + "$defs": { + "jwk_public_key": { + "type": "object", + "description": "EC public JSON Web Key used for HTTP Message Signatures and signed webhook verification. UCP profiles publish public keys only; private key material MUST NOT appear in a profile. Additional public JWK members are permitted; consumers ignore unknown members.", + "required": ["kid", "kty", "crv", "x", "y"], + "properties": { + "kid": { + "type": "string", + "description": "Key identifier referenced by Signature-Input keyid." + }, + "kty": { + "type": "string", + "const": "EC", + "description": "JWK key type. UCP profile signing keys use EC." + }, + "crv": { + "type": "string", + "description": "Elliptic curve name, e.g. P-256 or P-384." + }, + "x": { + "type": "string", + "description": "Public key x coordinate for EC keys." + }, + "y": { + "type": "string", + "description": "Public key y coordinate for EC keys." + }, + "use": { + "type": "string", + "description": "JWK public key use. UCP examples use sig for signatures." + }, + "alg": { + "type": "string", + "description": "JWA algorithm associated with this public key." + } + }, + "not": { + "anyOf": [ + { "required": ["d"] }, + { "required": ["p"] }, + { "required": ["q"] }, + { "required": ["dp"] }, + { "required": ["dq"] }, + { "required": ["qi"] }, + { "required": ["oth"] }, + { "required": ["k"] } + ] + }, + "additionalProperties": true + }, + + "base": { + "type": "object", + "description": "Common wrapper for UCP profile documents.", + "required": ["ucp"], + "properties": { + "ucp": { + "$ref": "ucp.json#/$defs/base", + "description": "Protocol metadata, capabilities, services, and payment handlers advertised by this party." + }, + "signing_keys": { + "type": "array", + "description": "Public keys used to verify signatures from this profile owner.", + "items": { "$ref": "#/$defs/jwk_public_key" } + } + }, + "additionalProperties": true + }, + + "business_schema": { + "title": "UCP Business Profile Document", + "description": "Profile document hosted by a business at /.well-known/ucp.", + "allOf": [ + { "$ref": "#/$defs/base" }, + { + "type": "object", + "properties": { + "ucp": { "$ref": "ucp.json#/$defs/business_schema" } + } + } + ] + }, + + "platform_schema": { + "title": "UCP Platform Profile Document", + "description": "Profile document hosted by a platform and advertised to businesses via UCP-Agent.", + "allOf": [ + { "$ref": "#/$defs/base" }, + { + "type": "object", + "properties": { + "ucp": { "$ref": "ucp.json#/$defs/platform_schema" } + } + } + ] + } + } +} diff --git a/protocol/schemas/service.json b/protocol/schemas/service.json index 09828164..88f64569 100644 --- a/protocol/schemas/service.json +++ b/protocol/schemas/service.json @@ -29,7 +29,7 @@ "platform_schema": { "title": "Service (Platform Schema)", - "description": "Full service declaration for platform-level discovery. All transports require `version`, `spec`, and `transport`. REST and MCP additionally require `schema` and `endpoint`; A2A requires `endpoint`; embedded requires `schema`.", + "description": "Full service declaration for platform-level discovery. All transports require `version`, `spec`, and `transport`. REST, MCP, and embedded additionally require `schema`.", "allOf": [ { "$ref": "#/$defs/base" }, { "required": ["spec"] }, @@ -37,15 +37,14 @@ "anyOf": [ { "properties": { "transport": { "const": "rest" } }, - "required": ["schema", "endpoint"] + "required": ["schema"] }, { "properties": { "transport": { "const": "mcp" } }, - "required": ["schema", "endpoint"] + "required": ["schema"] }, { - "properties": { "transport": { "const": "a2a" } }, - "required": ["endpoint"] + "properties": { "transport": { "const": "a2a" } } }, { "properties": { "transport": { "const": "embedded" } }, diff --git a/protocol/schemas/shopping/cart.json b/protocol/schemas/shopping/cart.json index 87524198..e7c45172 100644 --- a/protocol/schemas/shopping/cart.json +++ b/protocol/schemas/shopping/cart.json @@ -76,6 +76,13 @@ "update": "optional" } }, + "attribution": { + "$ref": "types/attribution.json", + "ucp_request": { + "create": "optional", + "update": "optional" + } + }, "buyer": { "$ref": "types/buyer.json", "description": "Optional buyer information for personalized estimates.", @@ -97,7 +104,7 @@ "messages": { "type": "array", "items": { - "$ref": "types/message.json" + "$ref": "../common/types/message.json" }, "description": "Validation messages, warnings, or informational notices.", "ucp_request": "omit" @@ -105,7 +112,7 @@ "links": { "type": "array", "items": { - "$ref": "types/link.json" + "$ref": "../common/types/link.json" }, "description": "Optional merchant links (policies, FAQs).", "ucp_request": "omit" diff --git a/protocol/schemas/shopping/catalog_lookup.json b/protocol/schemas/shopping/catalog_lookup.json new file mode 100644 index 00000000..2fe7e657 --- /dev/null +++ b/protocol/schemas/shopping/catalog_lookup.json @@ -0,0 +1,177 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/catalog_lookup.json", + "name": "dev.ucp.shopping.catalog.lookup", + "title": "Catalog Lookup", + "description": "Product/variant lookup by identifier. Supports batch retrieval (lookup_catalog) and single-product detail (get_product).", + "type": "object", + "$defs": { + "lookup_variant": { + "description": "Variant with required correlation metadata for lookup responses.", + "allOf": [ + { "$ref": "types/variant.json" }, + { + "required": ["inputs"], + "properties": { + "inputs": { + "type": "array", + "items": { "$ref": "types/input_correlation.json" }, + "minItems": 1, + "description": "Which request identifiers resolved to this variant, and how. Each entry maps a request ID to its match type." + } + } + } + ] + }, + "lookup_request": { + "type": "object", + "description": "Request body for catalog lookup.", + "required": ["ids"], + "properties": { + "ids": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1, + "description": "Identifiers to lookup. Implementations MUST support product ID and variant ID; MAY support secondary identifiers (SKU, handle, etc.)." + }, + "filters": { + "$ref": "types/search_filters.json", + "description": "Filter criteria to narrow returned products and variants. All specified filters combine with AND logic." + }, + "context": { + "$ref": "types/context.json" + }, + "signals": { + "$ref": "types/signals.json" + }, + "attribution": { + "$ref": "types/attribution.json" + } + } + }, + "lookup_response": { + "type": "object", + "required": [ + "ucp", + "products" + ], + "properties": { + "ucp": { + "$ref": "../ucp.json#/$defs/response_catalog_schema" + }, + "products": { + "type": "array", + "items": { + "allOf": [ + { "$ref": "types/product.json" }, + { + "properties": { + "variants": { + "items": { "$ref": "#/$defs/lookup_variant" } + } + } + } + ] + }, + "description": "Products matching the requested identifiers. May contain fewer items if some identifiers not found, or more if identifiers match multiple products." + }, + "messages": { + "type": "array", + "items": { + "$ref": "../common/types/message.json" + }, + "description": "Errors, warnings, or informational messages about the requested items." + } + } + }, + "get_product_request": { + "type": "object", + "description": "Request body for single-product retrieval. Supports interactive variant narrowing via selected and preferences.", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "description": "Product or variant identifier. Implementations MUST support product ID and variant ID." + }, + "selected": { + "type": "array", + "items": { + "$ref": "types/selected_option.json" + }, + "description": "Partial or full option selections for interactive variant narrowing. When provided, response option values include availability signals (available, exists) relative to these selections." + }, + "preferences": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Option names in relaxation priority order. When no exact variant matches all selections, the server drops options from the end of this list first. E.g., ['Color', 'Size'] keeps Color and relaxes Size." + }, + "filters": { + "$ref": "types/search_filters.json", + "description": "Filter criteria to narrow returned variants. All specified filters combine with AND logic." + }, + "context": { + "$ref": "types/context.json" + }, + "signals": { + "$ref": "types/signals.json" + }, + "attribution": { + "$ref": "types/attribution.json" + } + } + }, + "detail_product": { + "description": "A product in a get_product response, extended with effective selections and availability signals on option values.", + "type": "object", + "allOf": [{ "$ref": "types/product.json" }], + "properties": { + "selected": { + "type": "array", + "items": { "$ref": "types/selected_option.json" }, + "description": "Effective option selections that anchor the featured variant and availability signals. Required when the product has configurable options; may be empty or omitted for products with no option axes." + }, + "options": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "values"], + "properties": { + "name": { "type": "string" }, + "values": { + "type": "array", + "items": { "$ref": "types/detail_option_value.json" }, + "minItems": 1 + } + } + }, + "description": "Product options with availability signals relative to the effective selections." + } + } + }, + "get_product_response": { + "type": "object", + "required": [ + "ucp", + "product" + ], + "properties": { + "ucp": { + "$ref": "../ucp.json#/$defs/response_catalog_schema" + }, + "product": { + "$ref": "#/$defs/detail_product", + "description": "The requested product with full detail. Singular — this is a single-resource operation." + }, + "messages": { + "type": "array", + "items": { + "$ref": "../common/types/message.json" + }, + "description": "Warnings or informational messages about the product (e.g., price recently changed, limited availability)." + } + } + } + } +} diff --git a/protocol/schemas/shopping/catalog_search.json b/protocol/schemas/shopping/catalog_search.json new file mode 100644 index 00000000..46bdb267 --- /dev/null +++ b/protocol/schemas/shopping/catalog_search.json @@ -0,0 +1,63 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/catalog_search.json", + "name": "dev.ucp.shopping.catalog.search", + "title": "Catalog Search", + "description": "Product catalog search capability.", + "type": "object", + "$defs": { + "search_request": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Free-text search query." + }, + "context": { + "$ref": "types/context.json" + }, + "signals": { + "$ref": "types/signals.json" + }, + "attribution": { + "$ref": "types/attribution.json" + }, + "filters": { + "$ref": "types/search_filters.json" + }, + "pagination": { + "$ref": "../common/types/pagination.json#/$defs/request" + } + } + }, + "search_response": { + "type": "object", + "required": [ + "ucp", + "products" + ], + "properties": { + "ucp": { + "$ref": "../ucp.json#/$defs/response_catalog_schema" + }, + "products": { + "type": "array", + "items": { + "$ref": "types/product.json" + }, + "description": "Products matching the search criteria." + }, + "pagination": { + "$ref": "../common/types/pagination.json#/$defs/response" + }, + "messages": { + "type": "array", + "items": { + "$ref": "../common/types/message.json" + }, + "description": "Errors, warnings, or informational messages about the search results." + } + } + } + } +} diff --git a/protocol/schemas/shopping/checkout.json b/protocol/schemas/shopping/checkout.json index 2ff6ef7b..7b5aa69d 100644 --- a/protocol/schemas/shopping/checkout.json +++ b/protocol/schemas/shopping/checkout.json @@ -58,6 +58,10 @@ "$ref": "types/signals.json", "ucp_request": "optional" }, + "attribution": { + "$ref": "types/attribution.json", + "ucp_request": "optional" + }, "status": { "type": "string", "enum": [ @@ -84,7 +88,7 @@ "messages": { "type": "array", "items": { - "$ref": "types/message.json" + "$ref": "../common/types/message.json" }, "description": "List of messages with error and info about the checkout session state.", "ucp_request": "omit" @@ -92,7 +96,7 @@ "links": { "type": "array", "items": { - "$ref": "types/link.json" + "$ref": "../common/types/link.json" }, "description": "Links to be displayed by the platform (Privacy Policy, TOS). Mandatory for legal compliance.", "ucp_request": "omit" diff --git a/protocol/schemas/shopping/discount.json b/protocol/schemas/shopping/discount.json index f4dd7eea..04c81dd0 100644 --- a/protocol/schemas/shopping/discount.json +++ b/protocol/schemas/shopping/discount.json @@ -18,7 +18,7 @@ "description": "JSONPath to the allocation target (e.g., '$.line_items[0]', '$.totals.shipping')." }, "amount": { - "$ref": "types/amount.json", + "$ref": "../common/types/amount.json", "description": "Amount allocated to this target in ISO 4217 minor units." } } @@ -40,7 +40,7 @@ "description": "Human-readable discount name (e.g., 'Summer Sale 20% Off')." }, "amount": { - "$ref": "types/amount.json", + "$ref": "../common/types/amount.json", "description": "Total discount amount in ISO 4217 minor units." }, "automatic": { @@ -67,7 +67,7 @@ "description": "True if this discount requires additional verification." }, "eligibility": { - "$ref": "types/reverse_domain_name.json", + "$ref": "../common/types/reverse_domain_name.json", "description": "The eligibility claim accepted by the Business for this discount. Corresponds to a value from context.eligibility. Omitted for code-based and non-eligibility automatic discounts." }, "allocations": { @@ -92,11 +92,11 @@ }, "applied": { "type": "array", - "readOnly": true, "items": { "$ref": "#/$defs/applied_discount" }, - "description": "Discounts successfully applied (code-based and automatic)." + "description": "Discounts successfully applied (code-based and automatic).", + "ucp_request": "omit" } } }, diff --git a/protocol/schemas/shopping/fulfillment.json b/protocol/schemas/shopping/fulfillment.json index 7fe61190..f2440168 100644 --- a/protocol/schemas/shopping/fulfillment.json +++ b/protocol/schemas/shopping/fulfillment.json @@ -137,7 +137,7 @@ } }, { - "$ref": "types/error_response.json" + "$ref": "../common/types/error_response.json" } ] } diff --git a/protocol/schemas/shopping/order.json b/protocol/schemas/shopping/order.json index adf24640..51b0b41b 100644 --- a/protocol/schemas/shopping/order.json +++ b/protocol/schemas/shopping/order.json @@ -99,9 +99,14 @@ "messages": { "type": "array", "items": { - "$ref": "types/message.json" + "$ref": "../common/types/message.json" }, "description": "Business outcome messages (errors, warnings, informational). Present when the business needs to communicate status or issues to the platform." + }, + "attribution": { + "$ref": "types/attribution.json", + "description": "Snapshot of the attribution associated with the originating checkout. Read-only on the order.", + "ucp_request": "omit" } } } diff --git a/protocol/schemas/shopping/split_payments.json b/protocol/schemas/shopping/split_payments.json new file mode 100644 index 00000000..6e5ec820 --- /dev/null +++ b/protocol/schemas/shopping/split_payments.json @@ -0,0 +1,81 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/split_payments.json", + "name": "dev.ucp.shopping.split_payments", + "title": "Split Payments Extension", + "description": "Enables buyers to use multiple payment instruments for a single checkout.", + "requires": { + "protocol": { "min": "2026-01-23" }, + "capabilities": { + "dev.ucp.shopping.checkout": { "min": "2026-01-23" } + } + }, + "$defs": { + "instrument_group": { + "$ref": "types/instrument_group.json" + }, + "payment_instrument": { + "title": "Payment Instrument (Split Payments)", + "description": "Payment instrument extended with an optional per-instrument amount for split payments.", + "allOf": [ + { "$ref": "types/payment_instrument.json" }, + { + "type": "object", + "properties": { + "amount": { + "$ref": "../common/types/amount.json", + "description": "Contribution amount for this instrument in `checkout.currency` minor units (ISO 4217). On request: the platform's requested contribution (omit for open-amount). On response: the actual amount authorized or charged (omitted when not finally processed)." + } + } + } + ] + }, + "dev.ucp.shopping.checkout": { + "title": "Checkout with Split Payments", + "description": "Checkout extended with split payment instrument amounts.", + "allOf": [ + { "$ref": "checkout.json" }, + { + "type": "object", + "properties": { + "payment": { + "type": "object", + "properties": { + "instruments": { + "type": "array", + "items": { + "$ref": "#/$defs/payment_instrument" + }, + "description": "Payment instruments in allocation priority order.", + "ucp_request": { + "create": "optional", + "update": "optional", + "complete": "required" + } + } + } + } + } + } + ] + }, + "dev.ucp.shopping.split_payments": { + "business_schema": { + "title": "Split Payments Capability (Business)", + "description": "Business-level split payments capability configuration.", + "allOf": [ + { "$ref": "../capability.json#/$defs/business_schema" }, + { + "type": "object", + "properties": { + "config": { + "$ref": "types/business_split_payments_config.json", + "description": "Business split payments configuration" + } + } + } + ] + } + } + } +} diff --git a/protocol/schemas/shopping/types/attribution.json b/protocol/schemas/shopping/types/attribution.json new file mode 100644 index 00000000..fc752d70 --- /dev/null +++ b/protocol/schemas/shopping/types/attribution.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/types/attribution.json", + "title": "Attribution", + "description": "Platform-emitted referral and conversion-event context — campaign identifiers, click IDs, source/medium markers, etc. The same parameters platforms communicate via URL query parameters in browser-based flows.", + "type": "object", + "additionalProperties": { + "type": "string", + "description": "URL-style parameter value, encoded as a string. Numeric or boolean values MUST be string-encoded as they would be in a URL query string." + } +} diff --git a/protocol/schemas/shopping/types/business_split_payments_config.json b/protocol/schemas/shopping/types/business_split_payments_config.json new file mode 100644 index 00000000..72eaed04 --- /dev/null +++ b/protocol/schemas/shopping/types/business_split_payments_config.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/types/business_split_payments_config.json", + "title": "Business Split Payments Config", + "description": "Business-level configuration for split payments. Declaring the capability means multiple payment instruments are supported; this config declares which combinations are valid.", + "type": "object", + "required": ["allowed_combinations"], + "properties": { + "allowed_combinations": { + "type": "array", + "description": "Array of valid instrument combinations. Each combination is an array of instrument groups. A payment is valid if it matches any combination.", + "items": { + "type": "array", + "description": "A single valid combination: an array of instrument groups that together define the constraints. All groups must be satisfied (AND logic).", + "items": { + "$ref": "instrument_group.json" + }, + "minItems": 1 + }, + "minItems": 1 + } + } +} diff --git a/protocol/schemas/shopping/types/category.json b/protocol/schemas/shopping/types/category.json new file mode 100644 index 00000000..f6f4ff9c --- /dev/null +++ b/protocol/schemas/shopping/types/category.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/types/category.json", + "title": "Category", + "description": "A product category with optional taxonomy identifier.", + "type": "object", + "required": ["value"], + "properties": { + "value": { + "type": "string", + "description": "Category value or path (e.g., 'Apparel > Shirts', '1604')." + }, + "taxonomy": { + "type": "string", + "description": "Source taxonomy. Well-known values: `google_product_category`, `shopify`, `merchant`." + } + } +} diff --git a/protocol/schemas/shopping/types/context.json b/protocol/schemas/shopping/types/context.json index c5f1162a..a140c67f 100644 --- a/protocol/schemas/shopping/types/context.json +++ b/protocol/schemas/shopping/types/context.json @@ -35,7 +35,7 @@ "description": "Buyer claims about eligible benefits such as loyalty membership, payment instrument perks, and similar. Recognized claims MAY inform the Business response (e.g., member-only product availability, adjusted pricing in catalog, provisional discounts at cart or checkout). Businesses MUST ignore unrecognized values without error. Values MUST use reverse-domain naming (e.g., 'com.example.loyalty_gold', 'org.school.student') and MUST be non-identifying.", "uniqueItems": true, "items": { - "$ref": "reverse_domain_name.json" + "$ref": "../../common/types/reverse_domain_name.json" } } } diff --git a/protocol/schemas/shopping/types/detail_option_value.json b/protocol/schemas/shopping/types/detail_option_value.json new file mode 100644 index 00000000..9e11c74c --- /dev/null +++ b/protocol/schemas/shopping/types/detail_option_value.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/types/detail_option_value.json", + "title": "Detail Option Value", + "description": "An option value with availability signals relative to the current selections. Used in get_product responses where selected context exists.", + "type": "object", + "allOf": [{ "$ref": "option_value.json" }], + "properties": { + "available": { + "type": "boolean", + "description": "Whether a variant matching this value and the current option selections is purchasable." + }, + "exists": { + "type": "boolean", + "description": "Whether a variant matching this value and the current option selections exists in the catalog." + } + } +} diff --git a/protocol/schemas/shopping/types/expectation.json b/protocol/schemas/shopping/types/expectation.json index 40ceb9da..f063ab09 100644 --- a/protocol/schemas/shopping/types/expectation.json +++ b/protocol/schemas/shopping/types/expectation.json @@ -40,7 +40,7 @@ "description": "Delivery method type (shipping, pickup, digital)." }, "destination": { - "$ref": "postal_address.json", + "$ref": "../../common/types/postal_address.json", "description": "Delivery destination address." }, "description": { diff --git a/protocol/schemas/shopping/types/input_correlation.json b/protocol/schemas/shopping/types/input_correlation.json new file mode 100644 index 00000000..d9734fd3 --- /dev/null +++ b/protocol/schemas/shopping/types/input_correlation.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/types/input_correlation.json", + "title": "Input Correlation", + "description": "Maps a request identifier to the variant it resolved to, with match semantics.", + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "string", + "description": "The identifier from the lookup request that resolved to this variant." + }, + "match": { + "type": "string", + "description": "How the request identifier resolved to this variant. Well-known values: `exact` (input directly identifies this variant, e.g., variant ID, SKU), `featured` (server selected this variant as representative, e.g., product ID resolved to best match). Businesses MAY implement and provide additional resolution strategies.", + "examples": ["exact", "featured"] + } + } +} diff --git a/protocol/schemas/shopping/types/instrument_group.json b/protocol/schemas/shopping/types/instrument_group.json new file mode 100644 index 00000000..8501de66 --- /dev/null +++ b/protocol/schemas/shopping/types/instrument_group.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/types/instrument_group.json", + "title": "Instrument Group", + "description": "A constraint within an allowed combination that defines which instrument types can fill this group and how many are permitted.", + "type": "object", + "required": ["types"], + "properties": { + "types": { + "type": "array", + "description": "Instrument types accepted by this group (OR logic). Any listed type qualifies.", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "min": { + "type": "integer", + "minimum": 0, + "default": 0, + "description": "Minimum number of instruments required from this group. Defaults to 0 (optional)." + }, + "max": { + "type": "integer", + "minimum": 1, + "default": 1, + "description": "Maximum number of instruments allowed from this group. Defaults to 1. MUST be greater than or equal to `min`." + } + } +} diff --git a/protocol/schemas/shopping/types/item.json b/protocol/schemas/shopping/types/item.json index cde81e1a..e52258b9 100644 --- a/protocol/schemas/shopping/types/item.json +++ b/protocol/schemas/shopping/types/item.json @@ -19,7 +19,7 @@ "ucp_request": "omit" }, "price": { - "$ref": "amount.json", + "$ref": "../../common/types/amount.json", "description": "Unit price in ISO 4217 minor units.", "ucp_request": "omit" }, diff --git a/protocol/schemas/shopping/types/option_value.json b/protocol/schemas/shopping/types/option_value.json new file mode 100644 index 00000000..1ce5f702 --- /dev/null +++ b/protocol/schemas/shopping/types/option_value.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/types/option_value.json", + "title": "Option Value", + "description": "A selectable value for a product option.", + "type": "object", + "required": [ + "label" + ], + "properties": { + "id": { + "type": "string", + "description": "Optional server-assigned identifier for this option value. When present in a selected_option, the server SHOULD use it for matching instead of label." + }, + "label": { + "type": "string", + "description": "Display text for this option value (e.g., 'Small', 'Blue')." + } + } +} diff --git a/protocol/schemas/shopping/types/payment_instrument.json b/protocol/schemas/shopping/types/payment_instrument.json index aed22730..1d70fda3 100644 --- a/protocol/schemas/shopping/types/payment_instrument.json +++ b/protocol/schemas/shopping/types/payment_instrument.json @@ -23,7 +23,7 @@ "description": "The broad category of the instrument (e.g., 'card', 'tokenized_card'). Specific schemas will constrain this to a constant value." }, "billing_address": { - "$ref": "postal_address.json", + "$ref": "../../common/types/postal_address.json", "description": "The billing address associated with this payment method." }, "credential": { diff --git a/protocol/schemas/shopping/types/price_filter.json b/protocol/schemas/shopping/types/price_filter.json new file mode 100644 index 00000000..9b3cac87 --- /dev/null +++ b/protocol/schemas/shopping/types/price_filter.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/types/price_filter.json", + "title": "Price Filter", + "description": "Price range filter denominated in context.currency. When context.currency matches the presentment currency, businesses apply the filter directly. When it differs, businesses SHOULD convert filter values to the presentment currency before applying; if conversion is not supported, businesses MAY ignore the filter and SHOULD indicate this via a message. When context.currency is absent, filter denomination is ambiguous and businesses MAY ignore it.", + "type": "object", + "properties": { + "min": { + "$ref": "../../common/types/amount.json", + "description": "Minimum price in ISO 4217 minor units." + }, + "max": { + "$ref": "../../common/types/amount.json", + "description": "Maximum price in ISO 4217 minor units." + } + } +} diff --git a/protocol/schemas/shopping/types/price_range.json b/protocol/schemas/shopping/types/price_range.json new file mode 100644 index 00000000..e3e48d6e --- /dev/null +++ b/protocol/schemas/shopping/types/price_range.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/types/price_range.json", + "title": "Price Range", + "description": "A price range representing minimum and maximum values (e.g., across product variants).", + "type": "object", + "required": [ + "min", + "max" + ], + "properties": { + "min": { + "$ref": "../../common/types/price.json", + "description": "Minimum price in the range." + }, + "max": { + "$ref": "../../common/types/price.json", + "description": "Maximum price in the range." + } + } +} diff --git a/protocol/schemas/shopping/types/product.json b/protocol/schemas/shopping/types/product.json new file mode 100644 index 00000000..95c4fbdf --- /dev/null +++ b/protocol/schemas/shopping/types/product.json @@ -0,0 +1,89 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/types/product.json", + "title": "Product", + "description": "A product in the catalog with variants and options.", + "type": "object", + "required": [ + "id", + "title", + "description", + "price_range", + "variants" + ], + "properties": { + "id": { + "type": "string", + "description": "Global ID (GID) uniquely identifying this product." + }, + "handle": { + "type": "string", + "description": "URL-safe slug for SEO-friendly URLs (e.g., 'blue-runner-pro'). Use id for stable API references." + }, + "title": { + "type": "string", + "description": "Product title." + }, + "description": { + "$ref": "../../common/types/description.json", + "description": "Product description in one or more formats." + }, + "url": { + "type": "string", + "format": "uri", + "description": "Canonical product page URL." + }, + "categories": { + "type": "array", + "items": { + "$ref": "category.json" + }, + "description": "Product categories with optional taxonomy identifiers." + }, + "price_range": { + "$ref": "price_range.json", + "description": "Price range across all variants." + }, + "list_price_range": { + "$ref": "price_range.json", + "description": "List price range before discounts (for strikethrough display)." + }, + "media": { + "type": "array", + "items": { + "$ref": "../../common/types/media.json" + }, + "description": "Product media (images, videos, 3D models). First item is the featured media for listings." + }, + "options": { + "type": "array", + "items": { + "$ref": "product_option.json" + }, + "description": "Product options (Size, Color, etc.)." + }, + "variants": { + "type": "array", + "items": { + "$ref": "variant.json" + }, + "minItems": 1, + "description": "Purchasable variants of this product. First item is the featured variant for listings." + }, + "rating": { + "$ref": "rating.json", + "description": "Aggregate product rating." + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Product tags for categorization and search." + }, + "metadata": { + "type": "object", + "description": "Business-defined custom data extending the standard product model." + } + } +} diff --git a/protocol/schemas/shopping/types/product_option.json b/protocol/schemas/shopping/types/product_option.json new file mode 100644 index 00000000..32663ae9 --- /dev/null +++ b/protocol/schemas/shopping/types/product_option.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/types/product_option.json", + "title": "Product Option", + "description": "A product option such as size, color, or material.", + "type": "object", + "required": [ + "name", + "values" + ], + "properties": { + "name": { + "type": "string", + "description": "Option name (e.g., 'Size', 'Color')." + }, + "values": { + "type": "array", + "items": { + "$ref": "option_value.json" + }, + "minItems": 1, + "description": "Available values for this option." + } + } +} diff --git a/protocol/schemas/shopping/types/rating.json b/protocol/schemas/shopping/types/rating.json new file mode 100644 index 00000000..6b0b3b5f --- /dev/null +++ b/protocol/schemas/shopping/types/rating.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/types/rating.json", + "title": "Rating", + "description": "Product rating aggregate.", + "type": "object", + "required": [ + "value", + "scale_max" + ], + "properties": { + "value": { + "type": "number", + "minimum": 0, + "description": "Average rating value." + }, + "scale_min": { + "type": "number", + "minimum": 0, + "default": 1, + "description": "Minimum value on the rating scale (e.g., 1 for 1-5 stars)." + }, + "scale_max": { + "type": "number", + "minimum": 1, + "description": "Maximum value on the rating scale (e.g., 5 for 5-star)." + }, + "count": { + "type": "integer", + "minimum": 0, + "description": "Number of reviews contributing to the rating." + } + } +} diff --git a/protocol/schemas/shopping/types/retail_location.json b/protocol/schemas/shopping/types/retail_location.json index 8ec9c676..2e9d5d88 100644 --- a/protocol/schemas/shopping/types/retail_location.json +++ b/protocol/schemas/shopping/types/retail_location.json @@ -18,7 +18,7 @@ "description": "Location name (e.g., store name)." }, "address": { - "$ref": "postal_address.json", + "$ref": "../../common/types/postal_address.json", "description": "Physical address of the location." } } diff --git a/protocol/schemas/shopping/types/search_filters.json b/protocol/schemas/shopping/types/search_filters.json new file mode 100644 index 00000000..8380be58 --- /dev/null +++ b/protocol/schemas/shopping/types/search_filters.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/types/search_filters.json", + "title": "Search Filters", + "description": "Filter criteria to narrow search results. All specified filters combine with AND logic.", + "type": "object", + "properties": { + "categories": { + "type": "array", + "items": { "type": "string" }, + "description": "Filter by product categories (OR logic — matches products in any listed categories). Values match against the value field in product category entries. Valid values can be discovered from the categories field in search results, merchant documentation, or standard taxonomies that businesses may align with." + }, + "price": { + "$ref": "price_filter.json" + } + }, + "additionalProperties": true +} diff --git a/protocol/schemas/shopping/types/selected_option.json b/protocol/schemas/shopping/types/selected_option.json new file mode 100644 index 00000000..e35f03ad --- /dev/null +++ b/protocol/schemas/shopping/types/selected_option.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/types/selected_option.json", + "title": "Selected Option", + "description": "A specific option selection on a variant (e.g., Size: Large).", + "type": "object", + "required": [ + "name", + "label" + ], + "properties": { + "name": { + "type": "string", + "description": "Option name (e.g., 'Size')." + }, + "id": { + "type": "string", + "description": "Optional option value identifier from option_value.id. When present, the server SHOULD use it for matching; name and label remain required for display." + }, + "label": { + "type": "string", + "description": "Selected option label (e.g., 'Large')." + } + } +} diff --git a/protocol/schemas/shopping/types/shipping_destination.json b/protocol/schemas/shopping/types/shipping_destination.json index a452a6b2..ec77b978 100644 --- a/protocol/schemas/shopping/types/shipping_destination.json +++ b/protocol/schemas/shopping/types/shipping_destination.json @@ -7,7 +7,7 @@ "ucp_shared_request": true, "allOf": [ { - "$ref": "postal_address.json" + "$ref": "../../common/types/postal_address.json" }, { "type": "object", diff --git a/protocol/schemas/shopping/types/total.json b/protocol/schemas/shopping/types/total.json index a6361a88..42b1c9c0 100644 --- a/protocol/schemas/shopping/types/total.json +++ b/protocol/schemas/shopping/types/total.json @@ -20,7 +20,7 @@ "ucp_request": "omit" }, "amount": { - "$ref": "signed_amount.json", + "$ref": "../../common/types/signed_amount.json", "ucp_request": "omit" } }, diff --git a/protocol/schemas/shopping/types/totals.json b/protocol/schemas/shopping/types/totals.json index c2574e8f..693d9866 100644 --- a/protocol/schemas/shopping/types/totals.json +++ b/protocol/schemas/shopping/types/totals.json @@ -26,7 +26,7 @@ "description": "Human-readable label for this sub-line." }, "amount": { - "$ref": "signed_amount.json" + "$ref": "../../common/types/signed_amount.json" } }, "description": "Sub-line entry. Additional metadata MAY be included." diff --git a/protocol/schemas/shopping/types/variant.json b/protocol/schemas/shopping/types/variant.json new file mode 100644 index 00000000..d0033e7c --- /dev/null +++ b/protocol/schemas/shopping/types/variant.json @@ -0,0 +1,167 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/types/variant.json", + "title": "Variant", + "description": "A purchasable variant of a product with specific option selections.", + "type": "object", + "required": [ + "id", + "title", + "description", + "price" + ], + "properties": { + "id": { + "type": "string", + "description": "Global ID (GID) uniquely identifying this variant. Used as item.id in checkout." + }, + "sku": { + "type": "string", + "description": "Business-assigned identifier for inventory and fulfillment." + }, + "barcodes": { + "type": "array", + "items": { + "type": "object", + "required": ["type", "value"], + "properties": { + "type": { + "type": "string", + "description": "Barcode standard. Well-known values: UPC, EAN, ISBN, GTIN, JAN." + }, + "value": { + "type": "string", + "description": "Barcode value." + } + } + }, + "description": "Industry-standard product identifiers for cross-reference and correlation." + }, + "handle": { + "type": "string", + "description": "URL-safe variant handle/slug." + }, + "title": { + "type": "string", + "description": "Variant display title (e.g., 'Blue / Large')." + }, + "description": { + "$ref": "../../common/types/description.json", + "description": "Variant description in one or more formats." + }, + "url": { + "type": "string", + "format": "uri", + "description": "Canonical variant page URL." + }, + "categories": { + "type": "array", + "items": { + "$ref": "category.json" + }, + "description": "Variant categories with optional taxonomy identifiers." + }, + "price": { + "$ref": "../../common/types/price.json", + "description": "Current selling price." + }, + "list_price": { + "$ref": "../../common/types/price.json", + "description": "List price before discounts (for strikethrough display)." + }, + "unit_price": { + "type": "object", + "description": "Price per standard unit of measurement. MAY be omitted when unit pricing does not apply.", + "required": ["amount", "currency", "measure", "reference"], + "properties": { + "amount": { + "$ref": "../../common/types/amount.json", + "description": "Unit price in ISO 4217 minor units. Business MUST return precomputed unit price value: (variant.price / measure.value) * reference.value." + }, + "currency": { + "type": "string", + "pattern": "^[A-Z]{3}$", + "description": "ISO 4217 currency code." + }, + "measure": { + "type": "object", + "description": "Product quantity in packaging (e.g., 750ml bottle).", + "required": ["value", "unit"], + "properties": { + "value": { "type": "number", "description": "Package quantity." }, + "unit": { "type": "string", "description": "Unit of measurement." } + } + }, + "reference": { + "type": "object", + "description": "Denominator for unit price display (e.g., per 100ml, per 1kg).", + "required": ["value", "unit"], + "properties": { + "value": { "type": "integer", "description": "Reference quantity." }, + "unit": { "type": "string", "description": "Unit of measurement." } + } + } + } + }, + "availability": { + "type": "object", + "description": "Variant availability for purchase.", + "properties": { + "available": { + "type": "boolean", + "description": "Whether this variant can be purchased. See status for fulfillment details." + }, + "status": { + "type": "string", + "description": "Qualifies available with fulfillment state. Well-known values: `in_stock`, `backorder`, `preorder`, `out_of_stock`, `discontinued`." + } + } + }, + "options": { + "type": "array", + "items": { + "$ref": "selected_option.json" + }, + "description": "Option values that define this variant (e.g., Color: Blue, Size: Large)." + }, + "media": { + "type": "array", + "items": { + "$ref": "../../common/types/media.json" + }, + "description": "Variant media (images, videos, 3D models). First item is the featured media for listings." + }, + "rating": { + "$ref": "rating.json", + "description": "Variant rating." + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Variant tags for categorization and search." + }, + "metadata": { + "type": "object", + "description": "Business-defined custom data extending the standard variant model." + }, + "seller": { + "type": "object", + "description": "Optional seller context for this variant.", + "properties": { + "name": { + "type": "string", + "description": "Seller display name." + }, + "links": { + "type": "array", + "items": { + "$ref": "../../common/types/link.json" + }, + "description": "Seller policy and information links." + } + } + } + } +} diff --git a/protocol/schemas/transports/a2a_message.json b/protocol/schemas/transports/a2a_message.json new file mode 100644 index 00000000..3f2a1e64 --- /dev/null +++ b/protocol/schemas/transports/a2a_message.json @@ -0,0 +1,113 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/transports/a2a_message.json", + "title": "A2A UCP Message Envelope", + "description": "Minimal A2A envelope shapes used by UCP's A2A checkout binding. This schema validates UCP's transport mapping points — Agent Card extension advertisement, inbound A2A Message requests, and JSON-RPC responses carrying A2A Message results — without attempting to re-specify the full A2A protocol.", + "oneOf": [ + { "$ref": "#/$defs/agent_card" }, + { "$ref": "#/$defs/message_request" }, + { "$ref": "#/$defs/message_response" } + ], + "$defs": { + "extension": { + "type": "object", + "description": "A2A Agent Card extension advertisement for UCP.", + "required": ["uri"], + "properties": { + "uri": { + "type": "string", + "format": "uri", + "description": "Extension URI. UCP uses its versioned reference URI." + }, + "description": { "type": "string" }, + "params": { + "type": "object", + "description": "Extension parameters such as advertised UCP capabilities.", + "additionalProperties": true + } + }, + "additionalProperties": true + }, + "agent_card": { + "type": "object", + "description": "A2A Agent Card fragment advertising UCP support through extensions.", + "required": ["extensions"], + "properties": { + "extensions": { + "type": "array", + "items": { "$ref": "#/$defs/extension" }, + "minItems": 1 + } + }, + "additionalProperties": true + }, + "part": { + "type": "object", + "description": "A2A message part. UCP examples use text parts for natural language and data parts for structured UCP payloads.", + "properties": { + "type": { "type": "string" }, + "kind": { "type": "string" }, + "text": { "type": "string" }, + "data": { + "type": "object", + "description": "Structured data payload. UCP reserves a2a.ucp.* keys for UCP payloads." + } + }, + "additionalProperties": true + }, + "message": { + "type": "object", + "description": "A2A Message carrying natural-language or structured UCP data parts.", + "required": ["role", "parts", "messageId", "kind", "contextId"], + "properties": { + "role": { + "type": "string", + "enum": ["user", "agent"], + "description": "Message sender role." + }, + "parts": { + "type": "array", + "items": { "$ref": "#/$defs/part" }, + "minItems": 1 + }, + "messageId": { "type": "string" }, + "kind": { "type": "string", "const": "message" }, + "contextId": { "type": "string" } + }, + "additionalProperties": true + }, + "message_request": { + "description": "A2A message/send JSON-RPC request whose params carry a UCP-bearing Message from the platform to the business agent.", + "allOf": [ + { "$ref": "jsonrpc.json#/$defs/request" }, + { + "type": "object", + "required": ["params"], + "properties": { + "method": { "const": "message/send" }, + "params": { + "type": "object", + "required": ["message"], + "properties": { + "message": { "$ref": "#/$defs/message" } + }, + "additionalProperties": true + } + } + } + ] + }, + "message_response": { + "description": "JSON-RPC success response whose result is an A2A Message from the business agent.", + "allOf": [ + { "$ref": "jsonrpc.json#/$defs/success_response" }, + { + "type": "object", + "properties": { + "result": { "$ref": "#/$defs/message" } + } + } + ] + } + } +} diff --git a/protocol/schemas/transports/embedded_message.json b/protocol/schemas/transports/embedded_message.json new file mode 100644 index 00000000..bf0f14ef --- /dev/null +++ b/protocol/schemas/transports/embedded_message.json @@ -0,0 +1,55 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/transports/embedded_message.json", + "title": "Embedded Protocol Message Envelope", + "description": "JSON-RPC envelope for UCP Embedded Protocol (EP) messages exchanged between a host and an embedded context. This schema constrains the shared transport envelope and method namespace while leaving capability-specific params and result payloads to their capability schemas.", + "oneOf": [ + { "$ref": "#/$defs/request" }, + { "$ref": "#/$defs/response" }, + { "$ref": "#/$defs/error_response" } + ], + "$defs": { + "method": { + "type": "string", + "pattern": "^(ec|ep\\.cart)\\.[a-z][a-z0-9_]*(?:\\.[a-z][a-z0-9_]*)*$", + "description": "Embedded Protocol method name. Checkout methods use ec.* and cart methods use ep.cart.*." + }, + "request": { + "description": "Embedded Protocol request or notification envelope. Messages with id expect a response; messages without id are notifications.", + "allOf": [ + { "$ref": "jsonrpc.json#/$defs/request" }, + { + "type": "object", + "required": ["params"], + "properties": { + "method": { "$ref": "#/$defs/method" }, + "params": { + "type": "object", + "description": "Capability-specific EP parameters." + } + } + } + ] + }, + "response": { + "description": "Embedded Protocol success response envelope. UCP application-level success and error outcomes are both carried in result.ucp.status.", + "allOf": [ + { "$ref": "jsonrpc.json#/$defs/success_response" }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "description": "Capability-specific EP result. Application-level status is defined by capability schemas; see Embedded Protocol response handling.", + "additionalProperties": true + } + } + } + ] + }, + "error_response": { + "description": "JSON-RPC transport-level error response for EP messages. Application-level failures use the response result with result.ucp.status=error instead.", + "$ref": "jsonrpc.json#/$defs/error_response" + } + } +} diff --git a/protocol/schemas/transports/jsonrpc.json b/protocol/schemas/transports/jsonrpc.json new file mode 100644 index 00000000..c737fbb2 --- /dev/null +++ b/protocol/schemas/transports/jsonrpc.json @@ -0,0 +1,98 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/transports/jsonrpc.json", + "title": "JSON-RPC 2.0 Envelope", + "description": "Common JSON-RPC 2.0 transport envelope used by UCP JSON-RPC-based bindings. This schema intentionally validates only the protocol envelope; binding-specific params and result payloads are validated by transport-specific schemas or extracted UCP payload schemas.", + "oneOf": [ + { "$ref": "#/$defs/request" }, + { "$ref": "#/$defs/success_response" }, + { "$ref": "#/$defs/error_response" } + ], + "$defs": { + "id": { + "description": "JSON-RPC request identifier. Notifications omit id; responses echo the request id, or use null when the request id could not be determined.", + "oneOf": [ + { "type": "string" }, + { "type": "number" }, + { "type": "null" } + ] + }, + "error": { + "type": "object", + "description": "JSON-RPC transport-level error object. UCP business outcomes use result payloads with UCP messages instead of this object.", + "required": ["code", "message"], + "properties": { + "code": { + "type": "integer", + "description": "JSON-RPC error code. Standard codes are negative integers; UCP bindings reserve business errors for UCP messages." + }, + "message": { + "type": "string", + "description": "Short transport-level error description." + }, + "data": { + "description": "Optional machine-readable transport error details." + } + }, + "additionalProperties": true + }, + "request": { + "type": "object", + "description": "JSON-RPC request or notification envelope. Presence of id makes the message a request; absence of id makes it a notification.", + "required": ["jsonrpc", "method"], + "properties": { + "jsonrpc": { + "type": "string", + "const": "2.0", + "description": "JSON-RPC protocol version." + }, + "id": { "$ref": "#/$defs/id" }, + "method": { + "type": "string", + "minLength": 1, + "description": "Transport method name. Binding-specific schemas constrain the method namespace." + }, + "params": { + "description": "Method parameters. Binding-specific schemas define the object shape.", + "oneOf": [ + { "type": "object" }, + { "type": "array" } + ] + } + }, + "additionalProperties": false + }, + "success_response": { + "type": "object", + "description": "JSON-RPC success response envelope.", + "required": ["jsonrpc", "id", "result"], + "properties": { + "jsonrpc": { + "type": "string", + "const": "2.0", + "description": "JSON-RPC protocol version." + }, + "id": { "$ref": "#/$defs/id" }, + "result": { + "description": "Successful transport result. UCP bindings define the nested result payload." + } + }, + "additionalProperties": false + }, + "error_response": { + "type": "object", + "description": "JSON-RPC transport error response envelope. This is for protocol-level failures, not UCP application-level messages.", + "required": ["jsonrpc", "id", "error"], + "properties": { + "jsonrpc": { + "type": "string", + "const": "2.0", + "description": "JSON-RPC protocol version." + }, + "id": { "$ref": "#/$defs/id" }, + "error": { "$ref": "#/$defs/error" } + }, + "additionalProperties": false + } + } +} diff --git a/protocol/schemas/transports/mcp_tool_call.json b/protocol/schemas/transports/mcp_tool_call.json new file mode 100644 index 00000000..43a4376d --- /dev/null +++ b/protocol/schemas/transports/mcp_tool_call.json @@ -0,0 +1,108 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/transports/mcp_tool_call.json", + "title": "MCP Tool Call Envelope", + "description": "UCP's MCP transport envelope for JSON-RPC tools/call messages. The schema validates the MCP mapping layer: operation name in params.name, UCP metadata and domain arguments in params.arguments, and UCP output in result.structuredContent.", + "oneOf": [ + { "$ref": "#/$defs/request" }, + { "$ref": "#/$defs/response" }, + { "$ref": "jsonrpc.json#/$defs/error_response" } + ], + "$defs": { + "ucp_agent": { + "type": "object", + "description": "UCP-Agent metadata carried inside MCP tool arguments.", + "required": ["profile"], + "properties": { + "profile": { + "type": "string", + "format": "uri-reference", + "description": "Platform profile URI advertised to the business." + } + }, + "additionalProperties": true + }, + "meta": { + "type": "object", + "description": "UCP request metadata passed through MCP params.arguments.meta.", + "properties": { + "ucp-agent": { "$ref": "#/$defs/ucp_agent" }, + "idempotency-key": { + "type": "string", + "description": "Optional idempotency key for retry-safe mutating operations." + } + }, + "additionalProperties": true + }, + "arguments": { + "type": "object", + "description": "MCP tool arguments. UCP reserves meta for transport metadata; operation payload fields such as checkout, cart, order id, or catalog inputs are operation-specific.", + "properties": { + "meta": { "$ref": "#/$defs/meta" } + }, + "additionalProperties": true + }, + "request": { + "description": "MCP tools/call request envelope for invoking a UCP operation.", + "allOf": [ + { "$ref": "jsonrpc.json#/$defs/request" }, + { + "type": "object", + "required": ["params"], + "properties": { + "method": { "const": "tools/call" }, + "params": { + "type": "object", + "required": ["name", "arguments"], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "MCP tool name matching the UCP operation binding, such as create_checkout or get_cart." + }, + "arguments": { "$ref": "#/$defs/arguments" } + }, + "additionalProperties": true + } + } + } + ] + }, + "content_part": { + "type": "object", + "description": "MCP content part returned for clients that do not consume structuredContent.", + "required": ["type"], + "properties": { + "type": { "type": "string" }, + "text": { "type": "string" } + }, + "additionalProperties": true + }, + "response": { + "description": "MCP tools/call response envelope. UCP payloads are carried in result.structuredContent; content is compatibility output.", + "allOf": [ + { "$ref": "jsonrpc.json#/$defs/success_response" }, + { + "type": "object", + "properties": { + "result": { + "type": "object", + "required": ["structuredContent"], + "properties": { + "structuredContent": { + "type": "object", + "description": "The UCP response payload for the operation." + }, + "content": { + "type": "array", + "items": { "$ref": "#/$defs/content_part" } + } + }, + "additionalProperties": true + } + } + } + ] + } + } +} diff --git a/protocol/schemas/ucp.json b/protocol/schemas/ucp.json index 4d80342c..79716bee 100644 --- a/protocol/schemas/ucp.json +++ b/protocol/schemas/ucp.json @@ -39,7 +39,7 @@ "capabilities": { "type": "object", "description": "Required capability versions, keyed by capability name. Keys must be a subset of the extension's $defs keys.", - "propertyNames": { "$ref": "shopping/types/reverse_domain_name.json" }, + "propertyNames": { "$ref": "common/types/reverse_domain_name.json" }, "additionalProperties": { "$ref": "#/$defs/version_constraint" } } }, @@ -92,7 +92,7 @@ "services": { "type": "object", "description": "Service registry keyed by reverse-domain name.", - "propertyNames": { "$ref": "shopping/types/reverse_domain_name.json" }, + "propertyNames": { "$ref": "common/types/reverse_domain_name.json" }, "additionalProperties": { "type": "array", "items": { "$ref": "service.json#/$defs/base" } @@ -101,7 +101,7 @@ "capabilities": { "type": "object", "description": "Capability registry keyed by reverse-domain name.", - "propertyNames": { "$ref": "shopping/types/reverse_domain_name.json" }, + "propertyNames": { "$ref": "common/types/reverse_domain_name.json" }, "additionalProperties": { "type": "array", "items": { "$ref": "capability.json#/$defs/base" } @@ -110,7 +110,7 @@ "payment_handlers": { "type": "object", "description": "Payment handler registry keyed by reverse-domain name.", - "propertyNames": { "$ref": "shopping/types/reverse_domain_name.json" }, + "propertyNames": { "$ref": "common/types/reverse_domain_name.json" }, "additionalProperties": { "type": "array", "items": { "$ref": "payment_handler.json#/$defs/base" } diff --git a/protocol/services/shopping/embedded.openrpc.json b/protocol/services/shopping/embedded.openrpc.json index 4210fea0..d9f8a78c 100644 --- a/protocol/services/shopping/embedded.openrpc.json +++ b/protocol/services/shopping/embedded.openrpc.json @@ -68,7 +68,7 @@ } }, { - "$ref": "../../schemas/shopping/types/error_response.json" + "$ref": "../../schemas/common/types/error_response.json" } ] } @@ -102,7 +102,7 @@ } }, { - "$ref": "../../schemas/shopping/types/error_response.json" + "$ref": "../../schemas/common/types/error_response.json" } ] } @@ -118,7 +118,7 @@ "name": "error", "required": true, "schema": { - "$ref": "../../schemas/shopping/types/error_response.json", + "$ref": "../../schemas/common/types/error_response.json", "description": "Session-level error raised by the business." } } @@ -273,7 +273,7 @@ } }, { - "$ref": "../../schemas/shopping/types/error_response.json" + "$ref": "../../schemas/common/types/error_response.json" } ] } @@ -316,7 +316,7 @@ } }, { - "$ref": "../../schemas/shopping/types/error_response.json" + "$ref": "../../schemas/common/types/error_response.json" } ] } @@ -351,7 +351,7 @@ } }, { - "$ref": "../../schemas/shopping/types/error_response.json" + "$ref": "../../schemas/common/types/error_response.json" } ] } @@ -411,7 +411,7 @@ } }, { - "$ref": "../../schemas/shopping/types/error_response.json" + "$ref": "../../schemas/common/types/error_response.json" } ] } @@ -445,7 +445,7 @@ } }, { - "$ref": "../../schemas/shopping/types/error_response.json" + "$ref": "../../schemas/common/types/error_response.json" } ] } @@ -461,7 +461,7 @@ "name": "error", "required": true, "schema": { - "$ref": "../../schemas/shopping/types/error_response.json", + "$ref": "../../schemas/common/types/error_response.json", "description": "Session-level error raised by the business." } } diff --git a/protocol/source-lock.json b/protocol/source-lock.json index cf66bf16..03a9f725 100644 --- a/protocol/source-lock.json +++ b/protocol/source-lock.json @@ -7,10 +7,90 @@ "local": "protocol/schemas/capability.json", "upstream": "source/schemas/capability.json" }, + { + "local": "protocol/schemas/common/identity_linking.json", + "upstream": "source/schemas/common/identity_linking.json" + }, + { + "local": "protocol/schemas/common/loyalty.json", + "upstream": "source/schemas/common/loyalty.json" + }, + { + "local": "protocol/schemas/common/types/amount.json", + "upstream": "source/schemas/common/types/amount.json" + }, + { + "local": "protocol/schemas/common/types/description.json", + "upstream": "source/schemas/common/types/description.json" + }, + { + "local": "protocol/schemas/common/types/error_code.json", + "upstream": "source/schemas/common/types/error_code.json" + }, + { + "local": "protocol/schemas/common/types/error_response.json", + "upstream": "source/schemas/common/types/error_response.json" + }, + { + "local": "protocol/schemas/common/types/info_code.json", + "upstream": "source/schemas/common/types/info_code.json" + }, + { + "local": "protocol/schemas/common/types/link.json", + "upstream": "source/schemas/common/types/link.json" + }, + { + "local": "protocol/schemas/common/types/media.json", + "upstream": "source/schemas/common/types/media.json" + }, + { + "local": "protocol/schemas/common/types/message.json", + "upstream": "source/schemas/common/types/message.json" + }, + { + "local": "protocol/schemas/common/types/message_error.json", + "upstream": "source/schemas/common/types/message_error.json" + }, + { + "local": "protocol/schemas/common/types/message_info.json", + "upstream": "source/schemas/common/types/message_info.json" + }, + { + "local": "protocol/schemas/common/types/message_warning.json", + "upstream": "source/schemas/common/types/message_warning.json" + }, + { + "local": "protocol/schemas/common/types/pagination.json", + "upstream": "source/schemas/common/types/pagination.json" + }, + { + "local": "protocol/schemas/common/types/postal_address.json", + "upstream": "source/schemas/common/types/postal_address.json" + }, + { + "local": "protocol/schemas/common/types/price.json", + "upstream": "source/schemas/common/types/price.json" + }, + { + "local": "protocol/schemas/common/types/reverse_domain_name.json", + "upstream": "source/schemas/common/types/reverse_domain_name.json" + }, + { + "local": "protocol/schemas/common/types/signed_amount.json", + "upstream": "source/schemas/common/types/signed_amount.json" + }, + { + "local": "protocol/schemas/common/types/warning_code.json", + "upstream": "source/schemas/common/types/warning_code.json" + }, { "local": "protocol/schemas/payment_handler.json", "upstream": "source/schemas/payment_handler.json" }, + { + "local": "protocol/schemas/profile.json", + "upstream": "source/schemas/profile.json" + }, { "local": "protocol/schemas/service.json", "upstream": "source/schemas/service.json" @@ -27,6 +107,14 @@ "local": "protocol/schemas/shopping/cart.json", "upstream": "source/schemas/shopping/cart.json" }, + { + "local": "protocol/schemas/shopping/catalog_lookup.json", + "upstream": "source/schemas/shopping/catalog_lookup.json" + }, + { + "local": "protocol/schemas/shopping/catalog_search.json", + "upstream": "source/schemas/shopping/catalog_search.json" + }, { "local": "protocol/schemas/shopping/checkout.json", "upstream": "source/schemas/shopping/checkout.json" @@ -47,6 +135,10 @@ "local": "protocol/schemas/shopping/payment.json", "upstream": "source/schemas/shopping/payment.json" }, + { + "local": "protocol/schemas/shopping/split_payments.json", + "upstream": "source/schemas/shopping/split_payments.json" + }, { "local": "protocol/schemas/shopping/types/account_info.json", "upstream": "source/schemas/shopping/types/account_info.json" @@ -56,8 +148,8 @@ "upstream": "source/schemas/shopping/types/adjustment.json" }, { - "local": "protocol/schemas/shopping/types/amount.json", - "upstream": "source/schemas/shopping/types/amount.json" + "local": "protocol/schemas/shopping/types/attribution.json", + "upstream": "source/schemas/shopping/types/attribution.json" }, { "local": "protocol/schemas/shopping/types/available_payment_instrument.json", @@ -71,6 +163,10 @@ "local": "protocol/schemas/shopping/types/business_fulfillment_config.json", "upstream": "source/schemas/shopping/types/business_fulfillment_config.json" }, + { + "local": "protocol/schemas/shopping/types/business_split_payments_config.json", + "upstream": "source/schemas/shopping/types/business_split_payments_config.json" + }, { "local": "protocol/schemas/shopping/types/buyer.json", "upstream": "source/schemas/shopping/types/buyer.json" @@ -84,16 +180,16 @@ "upstream": "source/schemas/shopping/types/card_payment_instrument.json" }, { - "local": "protocol/schemas/shopping/types/context.json", - "upstream": "source/schemas/shopping/types/context.json" + "local": "protocol/schemas/shopping/types/category.json", + "upstream": "source/schemas/shopping/types/category.json" }, { - "local": "protocol/schemas/shopping/types/error_code.json", - "upstream": "source/schemas/shopping/types/error_code.json" + "local": "protocol/schemas/shopping/types/context.json", + "upstream": "source/schemas/shopping/types/context.json" }, { - "local": "protocol/schemas/shopping/types/error_response.json", - "upstream": "source/schemas/shopping/types/error_response.json" + "local": "protocol/schemas/shopping/types/detail_option_value.json", + "upstream": "source/schemas/shopping/types/detail_option_value.json" }, { "local": "protocol/schemas/shopping/types/expectation.json", @@ -127,6 +223,14 @@ "local": "protocol/schemas/shopping/types/fulfillment_option.json", "upstream": "source/schemas/shopping/types/fulfillment_option.json" }, + { + "local": "protocol/schemas/shopping/types/input_correlation.json", + "upstream": "source/schemas/shopping/types/input_correlation.json" + }, + { + "local": "protocol/schemas/shopping/types/instrument_group.json", + "upstream": "source/schemas/shopping/types/instrument_group.json" + }, { "local": "protocol/schemas/shopping/types/item.json", "upstream": "source/schemas/shopping/types/item.json" @@ -135,29 +239,13 @@ "local": "protocol/schemas/shopping/types/line_item.json", "upstream": "source/schemas/shopping/types/line_item.json" }, - { - "local": "protocol/schemas/shopping/types/link.json", - "upstream": "source/schemas/shopping/types/link.json" - }, { "local": "protocol/schemas/shopping/types/merchant_fulfillment_config.json", "upstream": "source/schemas/shopping/types/merchant_fulfillment_config.json" }, { - "local": "protocol/schemas/shopping/types/message.json", - "upstream": "source/schemas/shopping/types/message.json" - }, - { - "local": "protocol/schemas/shopping/types/message_error.json", - "upstream": "source/schemas/shopping/types/message_error.json" - }, - { - "local": "protocol/schemas/shopping/types/message_info.json", - "upstream": "source/schemas/shopping/types/message_info.json" - }, - { - "local": "protocol/schemas/shopping/types/message_warning.json", - "upstream": "source/schemas/shopping/types/message_warning.json" + "local": "protocol/schemas/shopping/types/option_value.json", + "upstream": "source/schemas/shopping/types/option_value.json" }, { "local": "protocol/schemas/shopping/types/order_confirmation.json", @@ -184,16 +272,36 @@ "upstream": "source/schemas/shopping/types/platform_fulfillment_config.json" }, { - "local": "protocol/schemas/shopping/types/postal_address.json", - "upstream": "source/schemas/shopping/types/postal_address.json" + "local": "protocol/schemas/shopping/types/price_filter.json", + "upstream": "source/schemas/shopping/types/price_filter.json" + }, + { + "local": "protocol/schemas/shopping/types/price_range.json", + "upstream": "source/schemas/shopping/types/price_range.json" + }, + { + "local": "protocol/schemas/shopping/types/product.json", + "upstream": "source/schemas/shopping/types/product.json" + }, + { + "local": "protocol/schemas/shopping/types/product_option.json", + "upstream": "source/schemas/shopping/types/product_option.json" + }, + { + "local": "protocol/schemas/shopping/types/rating.json", + "upstream": "source/schemas/shopping/types/rating.json" }, { "local": "protocol/schemas/shopping/types/retail_location.json", "upstream": "source/schemas/shopping/types/retail_location.json" }, { - "local": "protocol/schemas/shopping/types/reverse_domain_name.json", - "upstream": "source/schemas/shopping/types/reverse_domain_name.json" + "local": "protocol/schemas/shopping/types/search_filters.json", + "upstream": "source/schemas/shopping/types/search_filters.json" + }, + { + "local": "protocol/schemas/shopping/types/selected_option.json", + "upstream": "source/schemas/shopping/types/selected_option.json" }, { "local": "protocol/schemas/shopping/types/shipping_destination.json", @@ -203,10 +311,6 @@ "local": "protocol/schemas/shopping/types/signals.json", "upstream": "source/schemas/shopping/types/signals.json" }, - { - "local": "protocol/schemas/shopping/types/signed_amount.json", - "upstream": "source/schemas/shopping/types/signed_amount.json" - }, { "local": "protocol/schemas/shopping/types/token_credential.json", "upstream": "source/schemas/shopping/types/token_credential.json" @@ -219,10 +323,30 @@ "local": "protocol/schemas/shopping/types/totals.json", "upstream": "source/schemas/shopping/types/totals.json" }, + { + "local": "protocol/schemas/shopping/types/variant.json", + "upstream": "source/schemas/shopping/types/variant.json" + }, + { + "local": "protocol/schemas/transports/a2a_message.json", + "upstream": "source/schemas/transports/a2a_message.json" + }, { "local": "protocol/schemas/transports/embedded_config.json", "upstream": "source/schemas/transports/embedded_config.json" }, + { + "local": "protocol/schemas/transports/embedded_message.json", + "upstream": "source/schemas/transports/embedded_message.json" + }, + { + "local": "protocol/schemas/transports/jsonrpc.json", + "upstream": "source/schemas/transports/jsonrpc.json" + }, + { + "local": "protocol/schemas/transports/mcp_tool_call.json", + "upstream": "source/schemas/transports/mcp_tool_call.json" + }, { "local": "protocol/schemas/ucp.json", "upstream": "source/schemas/ucp.json"