Observed
On main 99572ca and in the published @ucp-js/sdk 0.5.1, UcpSchema (the ucp member of UcpDiscoveryProfileSchema) is generated as
export const UcpSchema = z.object({
capabilities: z.array(CapabilityDiscoverySchema),
services: z.record(z.string(), UcpServiceSchema),
version: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
});
Parsing the business profile example that the specification publishes at docs/specification/overview/index.md (release/2026-08-25, the block annotated ucp:example schema=profile def=business_schema, with the version macro rendered) gives
UcpDiscoveryProfileSchema REJECT ucp.capabilities: Expected array, received object
ucp.services.dev.ucp.shopping: Expected object, received array
UcpProfileDocumentSchema ACCEPT
UcpProfileDocumentSchema, generated in the same file from schemas/profile.json, accepts the same document. The two types in one package disagree about the shape of /.well-known/ucp.
Expected
ucp.json#/$defs/base (release/2026-08-25 and main) declares services, capabilities and payment_handlers each as an object keyed by reverse domain name whose values are arrays of entities, and requires only version. #/$defs/business_schema, which the profile example validates against, additionally requires services and payment_handlers and adds supported_versions. The discovery profile type should accept that shape and carry payment_handlers.
python-sdk types the same document correctly (Base.capabilities: dict[ReverseDomainName, list[capability.Base]], likewise services and payment_handlers, src/ucp_sdk/models/schemas/ucp.py).
Root cause
scripts/project-current-ucp-schemas.mjs writes the ucp node of the discovery profile by hand (writeCompatibilityDiscoverySchemas, the ucpDiscoveryProfile literal). It copies the release/2026-01-11 discovery shape, where capabilities really were a flat array with a name field. #16 kept that legacy surface deliberately at the time; #61 has since changed the legacy surface (signing_keys to keys), so it is no longer frozen, and the shape has now shipped in 0.5.0 and 0.5.1. The response envelope in the same script is already derived from ucp.json#/$defs/base through buildResponseEnvelopeSchema, so the derived and hand written types have diverged.
Prior report
#19 (artemii-karkusha, 2026-03-29) reported exactly these two divergences against live merchant profiles. It received no comments and was closed as completed by the reporter, citing commits that are not in this repository (they are in the reporter fork); #18 is the same report. The defect is unchanged since then. Credit for the finding belongs there.
Why CI does not catch it
tests.yml proves committed models match regeneration, not that they match the specification. The only discovery fixtures in tests/spec-constraints.test.js are { capabilities: [], services: {} }, which cannot distinguish the two shapes. No test parses a specification example.
Fix
A PR follows: derive the ucp node from #/$defs/business_schema the same way the envelope is derived, add the specification example as a fixture, and pin the shape in tests. Exported names are unchanged.
Observed
On main 99572ca and in the published @ucp-js/sdk 0.5.1,
UcpSchema(theucpmember ofUcpDiscoveryProfileSchema) is generated asParsing the business profile example that the specification publishes at
docs/specification/overview/index.md(release/2026-08-25, the block annotateducp:example schema=profile def=business_schema, with the version macro rendered) givesUcpProfileDocumentSchema, generated in the same file fromschemas/profile.json, accepts the same document. The two types in one package disagree about the shape of/.well-known/ucp.Expected
ucp.json#/$defs/base(release/2026-08-25 and main) declaresservices,capabilitiesandpayment_handlerseach as an object keyed by reverse domain name whose values are arrays of entities, and requires onlyversion.#/$defs/business_schema, which the profile example validates against, additionally requiresservicesandpayment_handlersand addssupported_versions. The discovery profile type should accept that shape and carrypayment_handlers.python-sdk types the same document correctly (
Base.capabilities: dict[ReverseDomainName, list[capability.Base]], likewise services and payment_handlers,src/ucp_sdk/models/schemas/ucp.py).Root cause
scripts/project-current-ucp-schemas.mjswrites theucpnode of the discovery profile by hand (writeCompatibilityDiscoverySchemas, theucpDiscoveryProfileliteral). It copies therelease/2026-01-11discovery shape, where capabilities really were a flat array with anamefield. #16 kept that legacy surface deliberately at the time; #61 has since changed the legacy surface (signing_keys to keys), so it is no longer frozen, and the shape has now shipped in 0.5.0 and 0.5.1. The response envelope in the same script is already derived fromucp.json#/$defs/basethroughbuildResponseEnvelopeSchema, so the derived and hand written types have diverged.Prior report
#19 (artemii-karkusha, 2026-03-29) reported exactly these two divergences against live merchant profiles. It received no comments and was closed as completed by the reporter, citing commits that are not in this repository (they are in the reporter fork); #18 is the same report. The defect is unchanged since then. Credit for the finding belongs there.
Why CI does not catch it
tests.ymlproves committed models match regeneration, not that they match the specification. The only discovery fixtures intests/spec-constraints.test.jsare{ capabilities: [], services: {} }, which cannot distinguish the two shapes. No test parses a specification example.Fix
A PR follows: derive the
ucpnode from#/$defs/business_schemathe same way the envelope is derived, add the specification example as a fixture, and pin the shape in tests. Exported names are unchanged.