portage-ucp 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/lib/generators/portage/ucp/install/templates/portage_ucp.rb +2 -2
- data/lib/portage/ucp/adapter.rb +1 -1
- data/lib/portage/ucp/capabilities/cart.rb +1 -0
- data/lib/portage/ucp/capabilities/catalog.rb +1 -0
- data/lib/portage/ucp/capabilities/checkout.rb +1 -0
- data/lib/portage/ucp/capabilities/discount.rb +1 -0
- data/lib/portage/ucp/capabilities/fulfillment.rb +1 -0
- data/lib/portage/ucp/capabilities/order.rb +1 -0
- data/lib/portage/ucp/capability.rb +7 -2
- data/lib/portage/ucp/configuration.rb +1 -1
- data/lib/portage/ucp/manifest.rb +31 -9
- data/lib/portage/ucp/schema_validator.rb +3 -3
- data/lib/portage/ucp/security/signature.rb +1 -1
- data/lib/portage/ucp/value_objects.rb +31 -13
- data/lib/portage/ucp/version.rb +1 -1
- data/lib/portage/ucp/wire_envelope.rb +1 -1
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/capability.json +11 -8
- data/schemas/2026-08-25/schemas/common/identity_linking.json +133 -0
- data/schemas/2026-08-25/schemas/common/location_lookup.json +108 -0
- data/schemas/2026-08-25/schemas/common/location_search.json +72 -0
- data/schemas/2026-08-25/schemas/common/loyalty.json +294 -0
- data/schemas/2026-08-25/schemas/common/payment_ap2_mandate.json +91 -0
- data/schemas/2026-08-25/schemas/common/payment_authentication.json +90 -0
- data/schemas/2026-08-25/schemas/common/payment_split_payments.json +82 -0
- data/schemas/2026-08-25/schemas/common/payment_terms.json +92 -0
- data/schemas/2026-08-25/schemas/common/types/actions.json +42 -0
- data/schemas/2026-08-25/schemas/common/types/amenity_type.json +13 -0
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/amount.json +4 -3
- data/schemas/2026-08-25/schemas/common/types/available_payment_instrument.json +20 -0
- data/schemas/2026-08-25/schemas/common/types/binding.json +22 -0
- data/schemas/2026-08-25/schemas/common/types/business_split_payments_config.json +25 -0
- data/schemas/2026-08-25/schemas/common/types/card_credential.json +78 -0
- data/schemas/2026-08-25/schemas/common/types/card_payment_instrument.json +77 -0
- data/schemas/2026-08-25/schemas/common/types/constraint_expression.json +71 -0
- data/schemas/2026-08-25/schemas/common/types/context.json +66 -0
- data/schemas/2026-08-25/schemas/common/types/daily_hour.json +36 -0
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/description.json +2 -2
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/error_code.json +3 -3
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/error_response.json +4 -4
- data/schemas/2026-08-25/schemas/common/types/exception_hour.json +38 -0
- data/schemas/2026-08-25/schemas/common/types/geo.json +25 -0
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/info_code.json +3 -3
- data/schemas/2026-08-25/schemas/common/types/instrument_group.json +32 -0
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/link.json +3 -3
- data/schemas/2026-08-25/schemas/common/types/locality.json +22 -0
- data/schemas/2026-08-25/schemas/common/types/location.json +93 -0
- data/schemas/2026-08-25/schemas/common/types/location_distance.json +23 -0
- data/schemas/2026-08-25/schemas/common/types/location_filter.json +43 -0
- data/schemas/2026-08-25/schemas/common/types/location_serves.json +80 -0
- data/schemas/2026-08-25/schemas/common/types/location_summary.json +29 -0
- data/schemas/2026-08-25/schemas/common/types/measure.json +25 -0
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/media.json +3 -3
- data/schemas/2026-08-25/schemas/common/types/message.json +18 -0
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/message_error.json +5 -5
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/message_info.json +4 -4
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/message_warning.json +4 -4
- data/schemas/2026-08-25/schemas/common/types/network_token_credential.json +68 -0
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/pagination.json +3 -4
- data/schemas/2026-08-25/schemas/common/types/pan_credential.json +54 -0
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common/types}/payment.json +3 -3
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/payment_credential.json +2 -2
- data/schemas/2026-08-25/schemas/common/types/payment_identity.json +16 -0
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/payment_instrument.json +5 -5
- data/schemas/2026-08-25/schemas/common/types/payment_schedule.json +37 -0
- data/schemas/2026-08-25/schemas/common/types/payment_term.json +35 -0
- data/schemas/2026-08-25/schemas/common/types/policy.json +34 -0
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/postal_address.json +2 -2
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/price.json +3 -3
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/price_filter.json +4 -4
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/price_range.json +5 -5
- data/schemas/2026-08-25/schemas/common/types/quantity_unit.json +22 -0
- data/schemas/2026-08-25/schemas/common/types/request_constraints.json +47 -0
- data/schemas/2026-08-25/schemas/common/types/reverse_domain_name.json +17 -0
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/signals.json +3 -3
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/signed_amount.json +5 -3
- data/schemas/2026-08-25/schemas/common/types/time_interval.json +29 -0
- data/schemas/2026-08-25/schemas/common/types/token_credential.json +29 -0
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/total.json +3 -3
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/totals.json +4 -4
- data/schemas/2026-08-25/schemas/common/types/unit.json +49 -0
- data/schemas/{2026-04-08/schemas/shopping → 2026-08-25/schemas/common}/types/warning_code.json +4 -3
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/payment_handler.json +4 -4
- data/schemas/2026-08-25/schemas/profile.json +266 -0
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/service.json +6 -6
- data/schemas/2026-08-25/schemas/shopping/buyer_consent.json +167 -0
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/cart.json +32 -37
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/catalog_lookup.json +46 -22
- data/schemas/2026-08-25/schemas/shopping/catalog_search.json +76 -0
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/checkout.json +29 -16
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/discount.json +9 -9
- data/schemas/2026-08-25/schemas/shopping/fulfillment.json +440 -0
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/order.json +19 -11
- data/schemas/2026-08-25/schemas/shopping/permalink.json +68 -0
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/adjustment.json +10 -4
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/attribution.json +2 -2
- data/schemas/2026-08-25/schemas/shopping/types/availability.json +17 -0
- data/schemas/2026-08-25/schemas/shopping/types/business_fulfillment_config.json +37 -0
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/buyer.json +2 -2
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/category.json +2 -2
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/detail_option_value.json +3 -3
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/expectation.json +6 -10
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/fulfillment.json +4 -5
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/fulfillment_available_method.json +3 -8
- data/schemas/2026-08-25/schemas/shopping/types/fulfillment_destination.json +55 -0
- data/schemas/2026-08-25/schemas/shopping/types/fulfillment_destination_filter.json +23 -0
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/fulfillment_event.json +4 -3
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/fulfillment_group.json +3 -3
- data/schemas/2026-08-25/schemas/shopping/types/fulfillment_method.json +118 -0
- data/schemas/2026-08-25/schemas/shopping/types/fulfillment_option.json +46 -0
- data/schemas/2026-08-25/schemas/shopping/types/fulfillment_option_base.json +29 -0
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/input_correlation.json +2 -2
- data/schemas/2026-08-25/schemas/shopping/types/item.json +43 -0
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/line_item.json +7 -6
- data/schemas/2026-08-25/schemas/shopping/types/location_destination.json +26 -0
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/option_value.json +2 -2
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/order_confirmation.json +2 -2
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/order_line_item.json +12 -9
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/platform_fulfillment_config.json +2 -2
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/product.json +10 -10
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/product_option.json +3 -3
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/rating.json +2 -2
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/search_filters.json +3 -3
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/selected_option.json +2 -2
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/shipping_destination.json +11 -5
- data/schemas/2026-08-25/schemas/shopping/types/unit_price.json +56 -0
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/shopping/types/variant.json +19 -78
- data/schemas/2026-08-25/schemas/transports/a2a_message.json +165 -0
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/transports/embedded_config.json +2 -2
- data/schemas/2026-08-25/schemas/transports/embedded_message.json +69 -0
- data/schemas/2026-08-25/schemas/transports/jsonrpc.json +136 -0
- data/schemas/2026-08-25/schemas/transports/mcp_tool_call.json +143 -0
- data/schemas/{2026-04-08 → 2026-08-25}/schemas/ucp.json +73 -23
- data/schemas/2026-08-25/services/common/mcp.openrpc.json +105 -0
- data/schemas/2026-08-25/services/common/rest.openapi.json +328 -0
- data/schemas/2026-08-25/services/payment-actions/embedded.openrpc.json +89 -0
- data/schemas/2026-08-25/services/shopping/embedded.openrpc.json +634 -0
- data/schemas/{2026-04-08 → 2026-08-25}/services/shopping/mcp.openrpc.json +21 -20
- data/schemas/2026-08-25/services/shopping/permalink.openapi.json +136 -0
- data/schemas/2026-08-25/services/shopping/rest.openapi.json +1350 -0
- metadata +153 -71
- data/schemas/2026-04-08/schemas/shopping/catalog_search.json +0 -64
- data/schemas/2026-04-08/schemas/shopping/fulfillment.json +0 -154
- data/schemas/2026-04-08/schemas/shopping/types/available_payment_instrument.json +0 -22
- data/schemas/2026-04-08/schemas/shopping/types/business_fulfillment_config.json +0 -38
- data/schemas/2026-04-08/schemas/shopping/types/context.json +0 -42
- data/schemas/2026-04-08/schemas/shopping/types/fulfillment_destination.json +0 -16
- data/schemas/2026-04-08/schemas/shopping/types/fulfillment_method.json +0 -67
- data/schemas/2026-04-08/schemas/shopping/types/fulfillment_option.json +0 -56
- data/schemas/2026-04-08/schemas/shopping/types/item.json +0 -33
- data/schemas/2026-04-08/schemas/shopping/types/message.json +0 -18
- data/schemas/2026-04-08/schemas/shopping/types/retail_location.json +0 -28
- data/schemas/2026-04-08/schemas/shopping/types/reverse_domain_name.json +0 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3030cd7b510d8e0e829878dd48ab11fd18f70e0d0e5cac5107852ead26a0077c
|
|
4
|
+
data.tar.gz: 7e6abed9cd7ea40da3ee5f8681ec69f80a6f8c852e8956901f4fce1b383816da
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4c4dddb8b6aaeaa674d270400287420f3bcbbfc028c8d38b9482fe53fe2cb47b493711cd7fffe50051b892b49f720f4332ba0e3af89fe595df9d46cdf80c5306
|
|
7
|
+
data.tar.gz: 5b1c1ee5a21fae8d7c8821c43820bcdcca15f54001e9d6e25af3b4916965da69a1bedb144faadb06ec222c11a59d3cd7e60c753579a4d8931924f51e62f06b78
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,20 @@ pre-1.0, so APIs may still shift between minor versions.
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.15.0] - 2026-10-13
|
|
10
|
+
|
|
11
|
+
- **Added: optional `policies:` on `Cart`, `Checkout`, `Order`, `Product`, `CatalogSearchResult` and `ProductDetail`** (UCP 2026-08-25 `policies[]`). An array of `policy.json` hashes (`type` and `description` required; `applies_to` and `url` optional), default `[]`, omitted from the wire payload when empty. `portage-ucp-shopify` 0.7.0 supplies it (design-log §60).
|
|
12
|
+
|
|
13
|
+
## [0.14.0] - 2026-10-09
|
|
14
|
+
|
|
15
|
+
Moves the gem to UCP 2026-08-25 (design-log §56-58). Minor-level for a pre-1.0 gem, with breaking config changes below.
|
|
16
|
+
|
|
17
|
+
- **Breaking: `config.payment_handlers` is now a Hash of `reverse-domain-key => [entry, ...]`, each entry carrying an `id`.** Default is `{}` (was an Array). The manifest emits it as UCP 2026-08-25's keyed `payment_handlers` object with date `version`s; an Array no longer works. `config.services` arrays still work: they are keyed by service name and each entry gets a `version`. A services Hash already keyed by name passes through, keeping an entry's own `version`.
|
|
18
|
+
- **Breaking: the manifest emits `keys` (a JWK Set) at the document root; `ucp.signing_keys` is gone.** Anything reading signing keys from `ucp.signing_keys` must read `keys`. Each key needs a `kid`.
|
|
19
|
+
- **Changed: UCP version is 2026-08-25** (manifest, `WireEnvelope`, `SchemaValidator` default). Capabilities are keyed by reverse-domain name with a date `version` and `schema` URL, and the whole manifest validates against `profile.json#/$defs/business_schema`.
|
|
20
|
+
- **Changed: `FulfillmentOption` `description` is sent as a `description.json` object** rather than a bare string, as 2026-08-25 requires.
|
|
21
|
+
- **Removed: the vendored `schemas/2026-04-08/` tree.** `SchemaValidator.new(version: "2026-04-08")` no longer loads; only `schemas/2026-08-25/` ships. Nothing else in the workspace validated against it (`portage-ucp-client` reads remote profiles without vendored schemas, so stores still serving 2026-04-08 keep working).
|
|
22
|
+
|
|
9
23
|
## [0.13.0] - 2026-10-07
|
|
10
24
|
|
|
11
25
|
- **Added: `Support::CheckoutState` can ask the platform about a checkout this process doesn't track.** An adapter may define a private `platform_checkout_order(checkout_id)` that returns the `Order` its platform placed from that checkout, or nil. `#checkout_status` then reports `completed` for an id with no in-process record, the new `#checkout_order` returns its `OrderConfirmation`, and the new `#checkout_from_platform_order` rebuilds a completed `Checkout` from the order when the platform has already dropped the cart. A found order is cached through `#record_checkout_status`/`#record_order_checkout`; no order, no hook, or a lookup error keeps today's `incomplete`. This lets `portage orders reconcile`, which runs in a new process, see a checkout the shopper paid for in the browser (design-log §55). The `Adapter` contract is unchanged. Minor-level.
|
|
@@ -14,8 +14,8 @@ Portage::Ucp.configure do |config|
|
|
|
14
14
|
|
|
15
15
|
# config.signer = MySigner.new
|
|
16
16
|
# config.signing_keys = []
|
|
17
|
-
# config.payment_handlers = []
|
|
18
|
-
# config.services = []
|
|
17
|
+
# config.payment_handlers = {} # { "com.example.pay" => [{ id: "..." }] }
|
|
18
|
+
# config.services = {} # { "dev.ucp.shopping" => [{ transport: "mcp", endpoint: "..." }] }
|
|
19
19
|
# config.mandate_trusted_keys = []
|
|
20
20
|
# config.require_mandate_signature = false
|
|
21
21
|
end
|
data/lib/portage/ucp/adapter.rb
CHANGED
|
@@ -105,7 +105,7 @@ module Portage
|
|
|
105
105
|
|
|
106
106
|
# --- Reorder (app.portage-ucp.reorder — Portage extension, not part of
|
|
107
107
|
# the UCP spec: dev.ucp.dev has no reorder/cart-hydration capability as
|
|
108
|
-
# of the 2026-
|
|
108
|
+
# of the 2026-08-25 spec) ---
|
|
109
109
|
# Hydrates a cart from a previous order's line items so a caller doesn't
|
|
110
110
|
# have to re-walk get_order + create_cart itself. Re-checks each item's
|
|
111
111
|
# current price/availability rather than replaying the order's snapshot
|
|
@@ -4,6 +4,7 @@ module Portage
|
|
|
4
4
|
CATALOG = Portage::Ucp::Capability.new(
|
|
5
5
|
name: "dev.ucp.shopping.catalog",
|
|
6
6
|
version: "1",
|
|
7
|
+
schema: "shopping/catalog_search.json",
|
|
7
8
|
actions: { "search_catalog" => :search_catalog, "get_product" => :get_product,
|
|
8
9
|
"lookup_catalog" => :lookup_catalog }
|
|
9
10
|
)
|
|
@@ -3,7 +3,7 @@ module Portage
|
|
|
3
3
|
# A named, versioned UCP capability (e.g. "dev.ucp.shopping.catalog") mapping
|
|
4
4
|
# UCP action names to the Adapter methods that back them.
|
|
5
5
|
class Capability
|
|
6
|
-
attr_reader :name, :version, :actions
|
|
6
|
+
attr_reader :name, :version, :actions, :schema
|
|
7
7
|
|
|
8
8
|
# `predicate:` is for extension capabilities like
|
|
9
9
|
# dev.ucp.shopping.discount and dev.ucp.shopping.fulfillment that add a
|
|
@@ -11,8 +11,13 @@ module Portage
|
|
|
11
11
|
# there's no dedicated method whose override signals support, so the
|
|
12
12
|
# adapter exposes a boolean method instead and the capability asks it
|
|
13
13
|
# directly rather than inspecting `actions`.
|
|
14
|
-
|
|
14
|
+
#
|
|
15
|
+
# `schema:` is the capability's schema path under a UCP revision's
|
|
16
|
+
# `schemas/` (e.g. "shopping/checkout.json"); the manifest turns it into
|
|
17
|
+
# the `schema` URL 2026-08-25 business profiles require.
|
|
18
|
+
def initialize(name:, version:, actions:, predicate: nil, schema: nil)
|
|
15
19
|
@name = name
|
|
20
|
+
@schema = schema
|
|
16
21
|
@version = version
|
|
17
22
|
@actions = actions
|
|
18
23
|
@predicate = predicate
|
|
@@ -51,7 +51,7 @@ module Portage
|
|
|
51
51
|
@authenticator = UnconfiguredAuthenticator.new
|
|
52
52
|
@rate_limiter = NullRateLimiter.new
|
|
53
53
|
@logger = Logger.new($stdout)
|
|
54
|
-
@payment_handlers =
|
|
54
|
+
@payment_handlers = {}
|
|
55
55
|
@signing_keys = []
|
|
56
56
|
@services = []
|
|
57
57
|
@require_mandate_signature = false
|
data/lib/portage/ucp/manifest.rb
CHANGED
|
@@ -6,11 +6,12 @@ module Portage
|
|
|
6
6
|
# Builds the /.well-known/ucp discovery document: protocol version, the
|
|
7
7
|
# business's advertised capabilities (only those the Adapter overrides —
|
|
8
8
|
# see Capability#advertised_for?), payment handlers, signing keys, and the
|
|
9
|
-
# services
|
|
9
|
+
# services (transport + endpoint, e.g. {transport: "mcp", endpoint:
|
|
10
10
|
# "https://..."}) a client needs to find where to actually connect.
|
|
11
11
|
# Signing keys are never generated here — see §9, consumer-provided only.
|
|
12
12
|
class Manifest
|
|
13
|
-
UCP_VERSION = "2026-
|
|
13
|
+
UCP_VERSION = "2026-08-25".freeze
|
|
14
|
+
SERVICE_KEY = "dev.ucp.shopping".freeze
|
|
14
15
|
|
|
15
16
|
# @param signer [#kid, #sign] optional. Consumer-provided — the gem never
|
|
16
17
|
# generates or stores keys itself (§9). `sign(canonical_json_string)`
|
|
@@ -35,26 +36,47 @@ module Portage
|
|
|
35
36
|
# capability name. That is the shape live Shopify UCP rollouts (Casper,
|
|
36
37
|
# Allbirds, Glossier, and 34+ others) serve under their own manifest
|
|
37
38
|
# "version": "2026-08-25", and the shape portage-ucp-client's
|
|
38
|
-
# Client.discover reads.
|
|
39
|
-
#
|
|
39
|
+
# Client.discover reads. As of 2026-08-25 the JWK Set is `keys` beside
|
|
40
|
+
# `ucp` (not `ucp.signing_keys`), and `services`, `capabilities` and
|
|
41
|
+
# `payment_handlers` are objects keyed by reverse-domain name whose
|
|
42
|
+
# entries carry a date `version` (capabilities also a `schema` URL,
|
|
43
|
+
# handlers an `id`). `services` still accepts the old bare array of
|
|
44
|
+
# entries, filed under SERVICE_KEY; `payment_handlers` must already be
|
|
45
|
+
# a Hash of key => [entry, ...].
|
|
40
46
|
def to_h
|
|
41
47
|
ucp = {
|
|
42
48
|
version: UCP_VERSION,
|
|
43
49
|
business: @business,
|
|
44
|
-
services:
|
|
50
|
+
services: keyed(service_hash),
|
|
45
51
|
capabilities: capability_hash,
|
|
46
|
-
payment_handlers: @payment_handlers
|
|
47
|
-
signing_keys: @signing_keys
|
|
52
|
+
payment_handlers: keyed(@payment_handlers)
|
|
48
53
|
}
|
|
49
54
|
ucp = ucp.merge(signature: sign(ucp)) if @signer
|
|
50
55
|
|
|
51
|
-
{ ucp: ucp }
|
|
56
|
+
{ ucp: ucp, keys: @signing_keys }
|
|
52
57
|
end
|
|
53
58
|
|
|
54
59
|
private
|
|
55
60
|
|
|
56
61
|
def capability_hash
|
|
57
|
-
@registry.advertised(@adapter).to_h
|
|
62
|
+
@registry.advertised(@adapter).to_h do |capability|
|
|
63
|
+
entry = { version: UCP_VERSION }
|
|
64
|
+
entry[:schema] = "https://ucp.dev/#{UCP_VERSION}/schemas/#{capability.schema}" if capability.schema
|
|
65
|
+
[capability.name, [entry]]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def service_hash
|
|
70
|
+
return @services if @services.is_a?(Hash)
|
|
71
|
+
|
|
72
|
+
@services.empty? ? {} : { SERVICE_KEY => @services }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Every entry needs a version; the caller's own wins.
|
|
76
|
+
def keyed(entries_by_key)
|
|
77
|
+
entries_by_key.to_h do |key, entries|
|
|
78
|
+
[key, [entries].flatten(1).map { |entry| { version: UCP_VERSION }.merge(entry) }]
|
|
79
|
+
end
|
|
58
80
|
end
|
|
59
81
|
|
|
60
82
|
def sign(payload)
|
|
@@ -9,12 +9,12 @@ module Portage
|
|
|
9
9
|
# is the CI-safe equivalent).
|
|
10
10
|
#
|
|
11
11
|
# The vendored tree mirrors https://ucp.dev/<version>/... path-for-path
|
|
12
|
-
# (schemas/2026-
|
|
13
|
-
# https://ucp.dev/2026-
|
|
12
|
+
# (schemas/2026-08-25/schemas/shopping/cart.json <->
|
|
13
|
+
# https://ucp.dev/2026-08-25/schemas/shopping/cart.json), so every $ref
|
|
14
14
|
# inside a vendored document — however deeply nested — resolves to another
|
|
15
15
|
# vendored file with a single prefix rewrite, no network access needed.
|
|
16
16
|
class SchemaValidator
|
|
17
|
-
def initialize(version: "2026-
|
|
17
|
+
def initialize(version: "2026-08-25", root: File.join(__dir__, "..", "..", "..", "schemas"))
|
|
18
18
|
@base_url = "https://ucp.dev/#{version}/"
|
|
19
19
|
@base_dir = File.expand_path(File.join(root, version))
|
|
20
20
|
end
|
|
@@ -7,7 +7,7 @@ module Portage
|
|
|
7
7
|
module Security
|
|
8
8
|
# Verifies that an inbound agent request carries a valid RFC 9421 HTTP
|
|
9
9
|
# Message Signature, per UCP's own signature spec
|
|
10
|
-
# (ucp.dev/2026-
|
|
10
|
+
# (ucp.dev/2026-08-25/specification/signatures/, pinned 2026-09-15 —
|
|
11
11
|
# this is the piece §22 of the design log flagged as unresearched
|
|
12
12
|
# before writing code, now checked against the live spec rather than
|
|
13
13
|
# guessed). This is a distinct concern from three other signing
|
|
@@ -115,9 +115,10 @@ module Portage
|
|
|
115
115
|
# (Shopify/Wix metafields etc.) — see Portage::Ucp::Shopify.configure's
|
|
116
116
|
# metadata_field.
|
|
117
117
|
Product = Data.define(:id, :title, :description, :price_range, :variants, :handle, :url, :categories,
|
|
118
|
-
:list_price_range, :media, :options, :tags, :metadata, :rating) do
|
|
118
|
+
:list_price_range, :media, :options, :tags, :metadata, :rating, :policies) do
|
|
119
119
|
def initialize(id:, title:, description:, price_range:, variants:, handle: nil, url: nil, categories: [],
|
|
120
|
-
list_price_range: nil, media: [], options: [], tags: [], metadata: nil, rating: nil
|
|
120
|
+
list_price_range: nil, media: [], options: [], tags: [], metadata: nil, rating: nil,
|
|
121
|
+
policies: [])
|
|
121
122
|
super
|
|
122
123
|
end
|
|
123
124
|
|
|
@@ -135,6 +136,7 @@ module Portage
|
|
|
135
136
|
h["media"] = media.map(&:to_wire_h) unless media.empty?
|
|
136
137
|
h["options"] = options.map(&:to_wire_h) unless options.empty?
|
|
137
138
|
h["tags"] = tags unless tags.empty?
|
|
139
|
+
h["policies"] = policies unless policies.empty?
|
|
138
140
|
h
|
|
139
141
|
end
|
|
140
142
|
end
|
|
@@ -144,12 +146,13 @@ module Portage
|
|
|
144
146
|
# isn't schema-conformant (search_response requires `ucp` + `products` at
|
|
145
147
|
# the top level, not a bare array). `messages` entries are left as plain
|
|
146
148
|
# wire-shaped hashes, same rationale as Variant#barcodes above.
|
|
147
|
-
CatalogSearchResult = Data.define(:products, :messages) do
|
|
148
|
-
def initialize(products:, messages: []) = super
|
|
149
|
+
CatalogSearchResult = Data.define(:products, :messages, :policies) do
|
|
150
|
+
def initialize(products:, messages: [], policies: []) = super
|
|
149
151
|
|
|
150
152
|
def to_wire_h
|
|
151
153
|
h = { "products" => products.map(&:to_wire_h) }
|
|
152
154
|
h["messages"] = messages unless messages.empty?
|
|
155
|
+
h["policies"] = policies unless policies.empty?
|
|
153
156
|
h
|
|
154
157
|
end
|
|
155
158
|
end
|
|
@@ -161,8 +164,14 @@ module Portage
|
|
|
161
164
|
# extension (interactive variant narrowing) — Adapter#get_product's
|
|
162
165
|
# signature has no `selected:`/`preferences:` params to source it from
|
|
163
166
|
# yet; unresearched, same status as design-log §16's other backlog items.
|
|
164
|
-
ProductDetail = Data.define(:product) do
|
|
165
|
-
def
|
|
167
|
+
ProductDetail = Data.define(:product, :policies) do
|
|
168
|
+
def initialize(product:, policies: []) = super
|
|
169
|
+
|
|
170
|
+
def to_wire_h
|
|
171
|
+
h = { "product" => product.to_wire_h }
|
|
172
|
+
h["policies"] = policies unless policies.empty?
|
|
173
|
+
h
|
|
174
|
+
end
|
|
166
175
|
end
|
|
167
176
|
|
|
168
177
|
# One cost-breakdown entry (schemas/shopping/types/total.json). `amount` is a
|
|
@@ -198,6 +207,10 @@ module Portage
|
|
|
198
207
|
end
|
|
199
208
|
end
|
|
200
209
|
|
|
210
|
+
# `policies` (cart/checkout/order/catalog responses and products) is the
|
|
211
|
+
# 2026-08-25 policy.json array (return/warranty terms), optional and omitted
|
|
212
|
+
# when empty. Entries stay wire-shaped hashes (`type` and `description` are
|
|
213
|
+
# required, `applies_to`/`url` optional), same posture as `messages`.
|
|
201
214
|
# schemas/shopping/types/link.json
|
|
202
215
|
Link = Data.define(:type, :url, :title) do
|
|
203
216
|
def initialize(type:, url:, title: nil) = super
|
|
@@ -272,6 +285,8 @@ module Portage
|
|
|
272
285
|
|
|
273
286
|
# schemas/shopping/types/fulfillment_option.json — a single priced choice
|
|
274
287
|
# within a FulfillmentGroup (e.g. "Standard Shipping $5", "Express $15").
|
|
288
|
+
# `description` is a common/types/description.json object
|
|
289
|
+
# ({"plain"/"html"/"markdown" => ...}); a bare String is sent as `plain`.
|
|
275
290
|
FulfillmentOption = Data.define(:id, :title, :totals, :description, :carrier, :earliest_fulfillment_time,
|
|
276
291
|
:latest_fulfillment_time) do
|
|
277
292
|
def initialize(id:, title:, totals:, description: nil, carrier: nil, earliest_fulfillment_time: nil,
|
|
@@ -281,7 +296,7 @@ module Portage
|
|
|
281
296
|
|
|
282
297
|
def to_wire_h
|
|
283
298
|
h = { "id" => id, "title" => title, "totals" => totals.map(&:to_wire_h) }
|
|
284
|
-
h["description"] = description if description
|
|
299
|
+
h["description"] = description.is_a?(String) ? { "plain" => description } : description if description
|
|
285
300
|
h["carrier"] = carrier if carrier
|
|
286
301
|
h["earliest_fulfillment_time"] = earliest_fulfillment_time if earliest_fulfillment_time
|
|
287
302
|
h["latest_fulfillment_time"] = latest_fulfillment_time if latest_fulfillment_time
|
|
@@ -378,13 +393,14 @@ module Portage
|
|
|
378
393
|
# `discounts` is the dev.ucp.shopping.discount extension (optional on both
|
|
379
394
|
# cart.json and its own schema) — omitted from the wire payload when empty,
|
|
380
395
|
# same as Order#adjustments.
|
|
381
|
-
Cart = Data.define(:id, :line_items, :currency, :totals, :discounts) do
|
|
382
|
-
def initialize(id:, line_items:, currency:, totals:, discounts: Discounts.new) = super
|
|
396
|
+
Cart = Data.define(:id, :line_items, :currency, :totals, :discounts, :policies) do
|
|
397
|
+
def initialize(id:, line_items:, currency:, totals:, discounts: Discounts.new, policies: []) = super
|
|
383
398
|
|
|
384
399
|
def to_wire_h
|
|
385
400
|
h = { "id" => id, "line_items" => line_items.map(&:to_wire_h), "currency" => currency,
|
|
386
401
|
"totals" => totals.map(&:to_wire_h) }
|
|
387
402
|
h["discounts"] = discounts.to_wire_h unless discounts.empty?
|
|
403
|
+
h["policies"] = policies unless policies.empty?
|
|
388
404
|
h
|
|
389
405
|
end
|
|
390
406
|
end
|
|
@@ -413,9 +429,9 @@ module Portage
|
|
|
413
429
|
# omitted from the wire payload when empty), same posture as
|
|
414
430
|
# Order#adjustments.
|
|
415
431
|
Checkout = Data.define(:id, :status, :line_items, :currency, :totals, :links, :order, :discounts,
|
|
416
|
-
:fulfillment) do
|
|
432
|
+
:fulfillment, :policies) do
|
|
417
433
|
def initialize(id:, status:, line_items:, currency:, totals:, links:, order: nil, discounts: Discounts.new,
|
|
418
|
-
fulfillment: CheckoutFulfillment.new)
|
|
434
|
+
fulfillment: CheckoutFulfillment.new, policies: [])
|
|
419
435
|
super
|
|
420
436
|
end
|
|
421
437
|
|
|
@@ -425,6 +441,7 @@ module Portage
|
|
|
425
441
|
h["order"] = order.to_wire_h if order
|
|
426
442
|
h["discounts"] = discounts.to_wire_h unless discounts.empty?
|
|
427
443
|
h["fulfillment"] = fulfillment.to_wire_h unless fulfillment.empty?
|
|
444
|
+
h["policies"] = policies unless policies.empty?
|
|
428
445
|
h
|
|
429
446
|
end
|
|
430
447
|
end
|
|
@@ -506,9 +523,9 @@ module Portage
|
|
|
506
523
|
# line_items/fulfillment/currency/totals. `adjustments` is optional —
|
|
507
524
|
# omitted from the wire payload when empty.
|
|
508
525
|
Order = Data.define(:id, :checkout_id, :permalink_url, :line_items, :fulfillment, :currency, :totals,
|
|
509
|
-
:adjustments) do
|
|
526
|
+
:adjustments, :policies) do
|
|
510
527
|
def initialize(id:, checkout_id:, permalink_url:, line_items:, fulfillment:, currency:, totals:,
|
|
511
|
-
adjustments: [])
|
|
528
|
+
adjustments: [], policies: [])
|
|
512
529
|
super
|
|
513
530
|
end
|
|
514
531
|
|
|
@@ -517,6 +534,7 @@ module Portage
|
|
|
517
534
|
"line_items" => line_items.map(&:to_wire_h), "fulfillment" => fulfillment.to_wire_h,
|
|
518
535
|
"currency" => currency, "totals" => totals.map(&:to_wire_h) }
|
|
519
536
|
h["adjustments"] = adjustments.map(&:to_wire_h) unless adjustments.empty?
|
|
537
|
+
h["policies"] = policies unless policies.empty?
|
|
520
538
|
h
|
|
521
539
|
end
|
|
522
540
|
end
|
data/lib/portage/ucp/version.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Portage
|
|
|
6
6
|
# capability's minimum envelope shape live in one place, not duplicated
|
|
7
7
|
# across value objects.
|
|
8
8
|
module WireEnvelope
|
|
9
|
-
SPEC_VERSION = "2026-
|
|
9
|
+
SPEC_VERSION = "2026-08-25".freeze
|
|
10
10
|
|
|
11
11
|
ENVELOPES = {
|
|
12
12
|
"dev.ucp.shopping.cart" => -> { { "version" => SPEC_VERSION } },
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://ucp.dev/2026-
|
|
3
|
+
"$id": "https://ucp.dev/2026-08-25/schemas/capability.json",
|
|
4
4
|
"title": "UCP Capability",
|
|
5
5
|
"description": "Schema for UCP capabilities and extensions. Extensions are capabilities with an 'extends' field. Uses reverse-domain naming for governance.",
|
|
6
6
|
"$defs": {
|
|
7
7
|
"base": {
|
|
8
8
|
"allOf": [
|
|
9
9
|
{
|
|
10
|
-
"$ref": "https://ucp.dev/2026-
|
|
10
|
+
"$ref": "https://ucp.dev/2026-08-25/schemas/ucp.json#/$defs/entity"
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
13
|
"type": "object",
|
|
@@ -15,14 +15,12 @@
|
|
|
15
15
|
"extends": {
|
|
16
16
|
"oneOf": [
|
|
17
17
|
{
|
|
18
|
-
"
|
|
19
|
-
"pattern": "^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$"
|
|
18
|
+
"$ref": "https://ucp.dev/2026-08-25/schemas/common/types/reverse_domain_name.json"
|
|
20
19
|
},
|
|
21
20
|
{
|
|
22
21
|
"type": "array",
|
|
23
22
|
"items": {
|
|
24
|
-
"
|
|
25
|
-
"pattern": "^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$"
|
|
23
|
+
"$ref": "https://ucp.dev/2026-08-25/schemas/common/types/reverse_domain_name.json"
|
|
26
24
|
},
|
|
27
25
|
"minItems": 1
|
|
28
26
|
}
|
|
@@ -50,10 +48,15 @@
|
|
|
50
48
|
},
|
|
51
49
|
"business_schema": {
|
|
52
50
|
"title": "Capability (Business Schema)",
|
|
53
|
-
"description": "Capability
|
|
51
|
+
"description": "Capability declaration for business/merchant discovery. Requires the `schema` URL so platforms can fetch and compose it during negotiation; may also include business-specific config overrides.",
|
|
54
52
|
"allOf": [
|
|
55
53
|
{
|
|
56
54
|
"$ref": "#/$defs/base"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"required": [
|
|
58
|
+
"schema"
|
|
59
|
+
]
|
|
57
60
|
}
|
|
58
61
|
]
|
|
59
62
|
},
|
|
@@ -67,4 +70,4 @@
|
|
|
67
70
|
]
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
|
-
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://ucp.dev/2026-08-25/schemas/common/identity_linking.json",
|
|
4
|
+
"name": "dev.ucp.common.identity_linking",
|
|
5
|
+
"title": "Identity Linking",
|
|
6
|
+
"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.",
|
|
7
|
+
"$defs": {
|
|
8
|
+
"scope_policy": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"title": "Scope Policy",
|
|
11
|
+
"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.",
|
|
12
|
+
"properties": {
|
|
13
|
+
"description": {
|
|
14
|
+
"$ref": "https://ucp.dev/2026-08-25/schemas/common/types/description.json",
|
|
15
|
+
"description": "Optional human-readable description of the scope that platforms can use to present and explain context (requirement and value) to the user."
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"additionalProperties": true
|
|
19
|
+
},
|
|
20
|
+
"scope_token": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"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.",
|
|
23
|
+
"pattern": "^[a-z](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9_-]*[a-z0-9_])?)+:[a-z][a-z0-9_]*$"
|
|
24
|
+
},
|
|
25
|
+
"provider": {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"title": "Identity Provider",
|
|
28
|
+
"description": "A trusted identity provider for delegated authentication, keyed by the 'type' discriminator. 'oauth2' denotes an OAuth 2.0 / OIDC authorization server. Future versions MAY define additional types (e.g. wallet attestation) as non-breaking extensions; platforms MUST treat entries whose 'type' they do not support as filtered out (see Provider Selection).",
|
|
29
|
+
"required": [
|
|
30
|
+
"type"
|
|
31
|
+
],
|
|
32
|
+
"properties": {
|
|
33
|
+
"type": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "Provider mechanism discriminator. 'oauth2' for OAuth 2.0 / OIDC authorization servers. Additional values MAY be defined by future versions; the value is an open string, not a closed enum, so unrecognized types remain valid and are filtered at runtime."
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"allOf": [
|
|
39
|
+
{
|
|
40
|
+
"if": {
|
|
41
|
+
"properties": {
|
|
42
|
+
"type": {
|
|
43
|
+
"const": "oauth2"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"then": {
|
|
48
|
+
"required": [
|
|
49
|
+
"auth_url"
|
|
50
|
+
],
|
|
51
|
+
"properties": {
|
|
52
|
+
"auth_url": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"format": "uri",
|
|
55
|
+
"description": "Base URL for authorization server metadata discovery. Platforms resolve metadata via RFC 8414 (/.well-known/oauth-authorization-server) with OIDC fallback (/.well-known/openid-configuration) on 404."
|
|
56
|
+
},
|
|
57
|
+
"required_claims": {
|
|
58
|
+
"type": "array",
|
|
59
|
+
"items": {
|
|
60
|
+
"type": "string"
|
|
61
|
+
},
|
|
62
|
+
"uniqueItems": true,
|
|
63
|
+
"description": "OIDC Core §5.1 claim names the business requires in the JWT authorization grant from this provider (e.g. 'email'). Optional pre-filter hint: platforms SHOULD skip this provider during selection when their upstream identity lacks any of the listed claim names. Reactive enforcement at the token endpoint remains mandatory regardless; this field expresses claim presence only, not value constraints."
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"additionalProperties": true
|
|
70
|
+
},
|
|
71
|
+
"dev.ucp.common.identity_linking": {
|
|
72
|
+
"platform_schema": {
|
|
73
|
+
"title": "Identity Linking (Platform)",
|
|
74
|
+
"description": "Platform-level identity linking capability declaration. Platforms advertise support for identity linking; no auth-specific config is required.",
|
|
75
|
+
"allOf": [
|
|
76
|
+
{
|
|
77
|
+
"$ref": "https://ucp.dev/2026-08-25/schemas/capability.json#/$defs/platform_schema"
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
"business_schema": {
|
|
82
|
+
"title": "Identity Linking (Business)",
|
|
83
|
+
"description": "Business-level identity linking configuration. Businesses declare the user-authenticated scopes they offer in 'config.scopes'.",
|
|
84
|
+
"allOf": [
|
|
85
|
+
{
|
|
86
|
+
"$ref": "https://ucp.dev/2026-08-25/schemas/capability.json#/$defs/business_schema"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"type": "object",
|
|
90
|
+
"required": [
|
|
91
|
+
"config"
|
|
92
|
+
],
|
|
93
|
+
"properties": {
|
|
94
|
+
"config": {
|
|
95
|
+
"type": "object",
|
|
96
|
+
"required": [
|
|
97
|
+
"scopes"
|
|
98
|
+
],
|
|
99
|
+
"properties": {
|
|
100
|
+
"providers": {
|
|
101
|
+
"type": "object",
|
|
102
|
+
"description": "Map of trusted external identity providers keyed by reverse-domain identifier. Each key maps to an array of mechanism entries — an IdP namespace MAY offer multiple token acquisition mechanisms. Declares which upstream IdPs the business will accept JWT bearer assertions from for the Accelerated IdP Flow (chaining via RFC 8693 + RFC 7523). This field is additive: direct OAuth against the business domain via RFC 8414 discovery is always available regardless of 'providers' content. Businesses MUST NOT list their own authorization server here — chaining-to-self is degenerate, and direct OAuth covers that path. When absent, empty, or when no listed mechanism is supported by the platform, platforms run direct OAuth on the business domain.",
|
|
103
|
+
"propertyNames": {
|
|
104
|
+
"$ref": "https://ucp.dev/2026-08-25/schemas/common/types/reverse_domain_name.json"
|
|
105
|
+
},
|
|
106
|
+
"additionalProperties": {
|
|
107
|
+
"type": "array",
|
|
108
|
+
"items": {
|
|
109
|
+
"$ref": "#/$defs/provider"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
"scopes": {
|
|
114
|
+
"type": "object",
|
|
115
|
+
"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).",
|
|
116
|
+
"propertyNames": {
|
|
117
|
+
"$ref": "#/$defs/scope_token"
|
|
118
|
+
},
|
|
119
|
+
"additionalProperties": {
|
|
120
|
+
"$ref": "#/$defs/scope_policy"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
"additionalProperties": true
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
"version": "2026-08-25"
|
|
133
|
+
}
|