Skip to content

UcpDiscoveryProfileSchema rejects the business profile example from the specification: capabilities typed as a flat array, services as single objects, payment_handlers absent #72

Description

@vishkaty

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions