11"""Regenerate the full cross-lang fixture corpus (Python side).
22
3- Writes all ``py-*.json`` fixtures under ``tests/fixtures/cross-lang/``. Used
4- after a canonicalization-relevant change (typ rename, capability-name rename,
5- schema-URL rename, key-sort tweak, etc. ) where every JWS in the corpus needs
3+ Writes all ``py-*.json`` fixtures under ``tests/fixtures/cross-lang/``. Used after a
4+ canonicalization-relevant change (typ rename, capability-name rename, schema-URL
5+ rename, key-sort tweak, profile-shape change ) where every JWS in the corpus needs
66to be re-signed.
77
8- Each scenario hand-crafts the profile body, signs with a fresh keypair, and
9- writes the ``{profile, jwks, alg, kid, generator}`` envelope. Cross-lang
10- verify in ``tests/test_ucp_cross_lang.py`` (and the Node sibling) pulls these
11- in alongside the ``node-*`` fixtures generated by the Node sibling.
8+ Each scenario hand-crafts the profile body using the spec-compliant input shape
9+ (``services`` / ``capabilities`` / ``payment_handlers`` as MAPS keyed by reverse-DNS
10+ service / capability / handler name), signs with a fresh keypair, and writes the
11+ ``{profile, jwks, alg, kid, generator}`` envelope. Cross-lang verify in
12+ ``tests/test_ucp_cross_lang.py`` (and the Node sibling) pulls these in alongside the
13+ ``node-*`` fixtures generated by the Node sibling.
14+
15+ Run: ``uv run python scripts/regenerate_cross_lang_fixtures.py``
1216"""
1317
1418from __future__ import annotations
2024from agentscore_commerce .identity import (
2125 AssessResult ,
2226 OperatorVerification ,
23- UCPCapability ,
24- UCPPaymentHandler ,
25- UCPService ,
27+ UCPCapabilityBinding ,
28+ UCPPaymentHandlerBinding ,
29+ UCPServiceBinding ,
2630 UCPSigningKey ,
2731 build_ucp_profile ,
2832)
@@ -51,82 +55,139 @@ def _envelope(signed: dict[str, Any], public_jwk: dict[str, Any], alg: str, kid:
5155 }
5256
5357
58+ # Spec-compliant binding helpers — each scenario uses these (or variants) so the
59+ # fixtures cover the full set of canonical UCP fields per binding type.
60+
61+
62+ def _shop_service_mcp (host : str ) -> UCPServiceBinding :
63+ return UCPServiceBinding (
64+ version = "2026-04-08" ,
65+ spec = "https://ucp.dev/2026-04-08/specification/overview" ,
66+ transport = "mcp" ,
67+ endpoint = f"{ host } /api/ucp/mcp" ,
68+ schema = "https://ucp.dev/services/shopping/openrpc.json" ,
69+ )
70+
71+
72+ def _shop_service_a2a (host : str ) -> UCPServiceBinding :
73+ return UCPServiceBinding (
74+ version = "2026-04-08" ,
75+ spec = "https://ucp.dev/2026-04-08/specification/overview" ,
76+ transport = "a2a" ,
77+ endpoint = f"{ host } /.well-known/agent-card.json" ,
78+ )
79+
80+
81+ def _tempo_handler (config : dict [str , Any ] | None = None ) -> UCPPaymentHandlerBinding :
82+ h = UCPPaymentHandlerBinding (
83+ id = "tempo" ,
84+ version = "2026-04-08" ,
85+ spec = "https://agentscore.sh/specification/payment-handlers/tempo" ,
86+ schema = "https://agentscore.sh/schemas/payment-handlers/tempo.json" ,
87+ )
88+ if config is not None :
89+ h .config = config
90+ return h
91+
92+
93+ def _x402_handler (networks : list [str ]) -> UCPPaymentHandlerBinding :
94+ return UCPPaymentHandlerBinding (
95+ id = "x402" ,
96+ version = "2026-04-08" ,
97+ spec = "https://agentscore.sh/specification/payment-handlers/x402" ,
98+ schema = "https://agentscore.sh/schemas/payment-handlers/x402.json" ,
99+ config = {"networks" : networks },
100+ )
101+
102+
103+ def _stripe_handler (config : dict [str , Any ]) -> UCPPaymentHandlerBinding :
104+ return UCPPaymentHandlerBinding (
105+ id = "stripe" ,
106+ version = "2026-04-08" ,
107+ spec = "https://agentscore.sh/specification/payment-handlers/stripe-spt" ,
108+ schema = "https://agentscore.sh/schemas/payment-handlers/stripe-spt.json" ,
109+ config = config ,
110+ )
111+
112+
54113def main () -> None :
55- # py-minimal
114+ # py-minimal — empty maps; just metadata + signing keys.
56115 kid = "py-minimal-EdDSA"
57116 key = generate_ucp_signing_key (kid = kid )
58117 profile = build_ucp_profile (
59- name = "Minimal Merchant" ,
60- services = [UCPService (type = "rest" , url = "https://m.example.com" )],
61- payment_handlers = [],
118+ services = {"dev.ucp.shopping" : [_shop_service_mcp ("https://m.example.com" )]},
62119 signing_keys = [UCPSigningKey .from_jwk (key .public_jwk )],
120+ name = "Minimal Merchant" ,
63121 )
64122 signed = sign_ucp_profile (profile .to_dict (), signing_key = key .private_key , kid = kid )
65123 _write ("py-minimal" , _envelope (signed , key .public_jwk , "EdDSA" , kid ))
66124
67- # py-es256-rails
125+ # py-es256-rails — multi-transport service + multi-rail + ES256 signing key.
68126 kid = "py-es256-rails-ES256"
69127 key = generate_ucp_signing_key (kid = kid , alg = "ES256" )
70128 profile = build_ucp_profile (
71- name = "ES256 Merchant" ,
72- services = [
73- UCPService (type = "rest" , url = "https://a.example.com" ),
74- UCPService (type = "a2a" , url = "https://a.example.com/agent-card.json" ),
75- ],
76- payment_handlers = [
77- UCPPaymentHandler (name = "tempo" , config = {"rail" : "tempo-mainnet" , "chain_id" : 4217 }),
78- UCPPaymentHandler (name = "x402" , config = {"networks" : ["base-8453" ]}),
79- ],
129+ services = {
130+ "dev.ucp.shopping" : [
131+ _shop_service_mcp ("https://a.example.com" ),
132+ _shop_service_a2a ("https://a.example.com" ),
133+ ],
134+ },
80135 signing_keys = [UCPSigningKey .from_jwk (key .public_jwk )],
136+ payment_handlers = {
137+ "sh.agentscore.payment.tempo" : [_tempo_handler ({"rail" : "tempo-mainnet" , "chain_id" : 4217 })],
138+ "sh.agentscore.payment.x402" : [_x402_handler (["base-8453" ])],
139+ },
140+ name = "ES256 Merchant" ,
81141 )
82142 signed = sign_ucp_profile (profile .to_dict (), signing_key = key .private_key , kid = kid , alg = "ES256" )
83143 _write ("py-es256-rails" , _envelope (signed , key .public_jwk , "ES256" , kid ))
84144
85- # py-extras-int
145+ # py-extras-int — payment_handler config with int + string fields.
86146 kid = "py-extras-int-EdDSA"
87147 key = generate_ucp_signing_key (kid = kid )
88148 profile = build_ucp_profile (
89- name = "Extras Merchant" ,
90- services = [UCPService (type = "rest" , url = "https://e.example.com" )],
91- payment_handlers = [UCPPaymentHandler (name = "stripe" , config = {"profile_id" : "abc" , "count" : 7 })],
149+ services = {"dev.ucp.shopping" : [_shop_service_mcp ("https://e.example.com" )]},
92150 signing_keys = [UCPSigningKey .from_jwk (key .public_jwk )],
151+ payment_handlers = {
152+ "sh.agentscore.payment.stripe-spt" : [_stripe_handler ({"profile_id" : "abc" , "count" : 7 })],
153+ },
154+ name = "Extras Merchant" ,
93155 )
94156 signed = sign_ucp_profile (profile .to_dict (), signing_key = key .private_key , kid = kid )
95157 _write ("py-extras-int" , _envelope (signed , key .public_jwk , "EdDSA" , kid ))
96158
97- # py-capability — hand-crafted vendor capability (renamed to
98- # sh.agentscore.identity to match the new namespace; the in-fixture name
99- # is independent of the SDK's auto-injection but consistency keeps the
100- # corpus honest about what callers should publish).
159+ # py-capability — hand-crafted vendor capability under sh.agentscore.identity.
101160 kid = "py-capability-EdDSA"
102161 key = generate_ucp_signing_key (kid = kid )
162+ custom_capability = UCPCapabilityBinding (
163+ version = "1" ,
164+ spec = "https://agentscore.sh/specification/identity" ,
165+ schema = "https://agentscore.sh/schemas/ucp/sh-agentscore-identity-v1.json" ,
166+ # `extras` flat on the binding — kyc_required is a vendor field on this binding.
167+ extras = {"kyc_required" : True },
168+ )
103169 profile = build_ucp_profile (
104- name = "Capability Merchant" ,
105- services = [UCPService (type = "rest" , url = "https://c.example.com" )],
106- capabilities = [
107- UCPCapability (
108- name = "sh.agentscore.identity" ,
109- schema = "https://agentscore.sh/schemas/ucp/sh-agentscore-identity-v1.json" ,
110- version = "1" ,
111- extras = {"kyc_required" : True },
112- ),
113- ],
114- payment_handlers = [
115- UCPPaymentHandler (name = "tempo" , config = {"rail" : "tempo-mainnet" , "chain_id" : 4217 }),
116- ],
170+ services = {"dev.ucp.shopping" : [_shop_service_mcp ("https://c.example.com" )]},
117171 signing_keys = [UCPSigningKey .from_jwk (key .public_jwk )],
172+ capabilities = {"sh.agentscore.identity" : [custom_capability ]},
173+ payment_handlers = {
174+ "sh.agentscore.payment.tempo" : [_tempo_handler ({"rail" : "tempo-mainnet" , "chain_id" : 4217 })],
175+ },
176+ name = "Capability Merchant" ,
118177 )
119178 signed = sign_ucp_profile (profile .to_dict (), signing_key = key .private_key , kid = kid )
120179 _write ("py-capability" , _envelope (signed , key .public_jwk , "EdDSA" , kid ))
121180
122- # py-unicode
181+ # py-unicode — multi-byte UTF-8 in name / endpoint / config.
123182 kid = "py-unicode-EdDSA"
124183 key = generate_ucp_signing_key (kid = kid )
125184 profile = build_ucp_profile (
126- name = "Café 日本 🍷 Merchant" ,
127- services = [UCPService (type = "rest" , url = "https://日本.example.com" )],
128- payment_handlers = [UCPPaymentHandler (name = "tempo" , config = {"note" : "メモ" })],
185+ services = {"dev.ucp.shopping" : [_shop_service_mcp ("https://日本.example.com" )]},
129186 signing_keys = [UCPSigningKey .from_jwk (key .public_jwk )],
187+ payment_handlers = {
188+ "sh.agentscore.payment.tempo" : [_tempo_handler ({"note" : "メモ" })],
189+ },
190+ name = "Café 日本 🍷 Merchant" ,
130191 )
131192 signed = sign_ucp_profile (profile .to_dict (), signing_key = key .private_key , kid = kid )
132193 _write ("py-unicode" , _envelope (signed , key .public_jwk , "EdDSA" , kid ))
@@ -135,13 +196,15 @@ def main() -> None:
135196 old_key = generate_ucp_signing_key (kid = "py-multikey-old" )
136197 new_key = generate_ucp_signing_key (kid = "py-multikey-new" )
137198 profile = build_ucp_profile (
138- name = "Multi-Key Merchant" ,
139- services = [UCPService (type = "rest" , url = "https://mk.example.com" )],
140- payment_handlers = [UCPPaymentHandler (name = "tempo" , config = {"rail" : "tempo-mainnet" })],
199+ services = {"dev.ucp.shopping" : [_shop_service_mcp ("https://mk.example.com" )]},
141200 signing_keys = [
142201 UCPSigningKey .from_jwk (old_key .public_jwk ),
143202 UCPSigningKey .from_jwk (new_key .public_jwk ),
144203 ],
204+ payment_handlers = {
205+ "sh.agentscore.payment.tempo" : [_tempo_handler ({"rail" : "tempo-mainnet" })],
206+ },
207+ name = "Multi-Key Merchant" ,
145208 )
146209 signed = sign_ucp_profile (profile .to_dict (), signing_key = new_key .private_key , kid = "py-multikey-new" )
147210 _write (
@@ -155,33 +218,35 @@ def main() -> None:
155218 },
156219 )
157220
158- # py-emoji-keys — extras with non-ASCII object keys (BMP private use, CJK
159- # compatibility, supplementary plane). Exercises codepoint-vs-UTF-16 sort.
221+ # py-emoji-keys — extras at top-level (outside the `ucp` envelope) with non-ASCII
222+ # object keys (BMP private use, CJK compatibility, supplementary plane).
223+ # Exercises codepoint-vs-UTF-16 sort.
160224 kid = "py-emoji-keys-EdDSA"
161225 key = generate_ucp_signing_key (kid = kid )
162226 profile = build_ucp_profile (
163- name = "Emoji Keys Merchant" ,
164- services = [UCPService (type = "rest" , url = "https://emoji.example.com" )],
165- payment_handlers = [UCPPaymentHandler (name = "tempo" , config = {})],
227+ services = {"dev.ucp.shopping" : [_shop_service_mcp ("https://emoji.example.com" )]},
166228 signing_keys = [UCPSigningKey .from_jwk (key .public_jwk )],
229+ payment_handlers = {
230+ "sh.agentscore.payment.tempo" : [_tempo_handler ()],
231+ },
232+ name = "Emoji Keys Merchant" ,
167233 extras = {
168234 "a" : 1 ,
169235 "豈" : 2 ,
170- " " : 3 ,
236+ "" : 3 ,
171237 "🍷" : 4 ,
172238 },
173239 )
174240 signed = sign_ucp_profile (profile .to_dict (), signing_key = key .private_key , kid = kid )
175241 _write ("py-emoji-keys" , _envelope (signed , key .public_jwk , "EdDSA" , kid ))
176242
177- # py-int-boundary — exercises Number.MAX_SAFE_INTEGER round-trip.
243+ # py-int-boundary — exercises Number.MAX_SAFE_INTEGER round-trip via top-level extras .
178244 kid = "py-int-boundary-EdDSA"
179245 key = generate_ucp_signing_key (kid = kid )
180246 profile = build_ucp_profile (
181- name = "Int Boundary Merchant" ,
182- services = [UCPService (type = "rest" , url = "https://i.example.com" )],
183- payment_handlers = [],
247+ services = {"dev.ucp.shopping" : [_shop_service_mcp ("https://i.example.com" )]},
184248 signing_keys = [UCPSigningKey .from_jwk (key .public_jwk )],
249+ name = "Int Boundary Merchant" ,
185250 extras = {
186251 "max_safe_int" : 9007199254740991 ,
187252 "min_safe_int" : - 9007199254740991 ,
@@ -193,9 +258,9 @@ def main() -> None:
193258 signed = sign_ucp_profile (profile .to_dict (), signing_key = key .private_key , kid = kid )
194259 _write ("py-int-boundary" , _envelope (signed , key .public_jwk , "EdDSA" , kid ))
195260
196- # py-data-driven-claims — exercises the build_ucp_profile data path with
197- # API-shape "missing" sentinels (empty string + None). Both languages MUST
198- # emit identical canonical bytes for this input.
261+ # py-data-driven-claims — exercises build_ucp_profile data path with API-shape
262+ # "missing" sentinels (empty string + None). Both languages MUST emit identical
263+ # canonical bytes for this input.
199264 kid = "py-data-driven-claims-EdDSA"
200265 key = generate_ucp_signing_key (kid = kid )
201266 result = AssessResult (
@@ -213,17 +278,16 @@ def main() -> None:
213278 },
214279 )
215280 profile = build_ucp_profile (
216- name = "Data Driven Claims Merchant" ,
217- services = [UCPService (type = "rest" , url = "https://d.example.com" )],
218- payment_handlers = [],
281+ services = {"dev.ucp.shopping" : [_shop_service_mcp ("https://d.example.com" )]},
219282 signing_keys = [UCPSigningKey .from_jwk (key .public_jwk )],
283+ name = "Data Driven Claims Merchant" ,
220284 data = result ,
221285 )
222286 signed = sign_ucp_profile (profile .to_dict (), signing_key = key .private_key , kid = kid )
223287 _write ("py-data-driven-claims" , _envelope (signed , key .public_jwk , "EdDSA" , kid ))
224288
225- # py-typed-claims — exercises the typed AssessResult fields (no raw
226- # fallback). Cross-lang parity check for the typed-field-only call site.
289+ # py-typed-claims — exercises typed AssessResult fields (no raw fallback).
290+ # Cross-lang parity check for the typed-field-only call site.
227291 kid = "py-typed-claims-EdDSA"
228292 key = generate_ucp_signing_key (kid = kid )
229293 result = AssessResult (
@@ -245,10 +309,9 @@ def main() -> None:
245309 raw = None ,
246310 )
247311 profile = build_ucp_profile (
248- name = "Typed Claims Merchant" ,
249- services = [UCPService (type = "rest" , url = "https://t.example.com" )],
250- payment_handlers = [],
312+ services = {"dev.ucp.shopping" : [_shop_service_mcp ("https://t.example.com" )]},
251313 signing_keys = [UCPSigningKey .from_jwk (key .public_jwk )],
314+ name = "Typed Claims Merchant" ,
252315 data = result ,
253316 )
254317 signed = sign_ucp_profile (profile .to_dict (), signing_key = key .private_key , kid = kid )
0 commit comments