fix: derive the discovery profile ucp member from ucp.json business_schema - #73
Open
vishkaty wants to merge 1 commit into
Open
Conversation
…chema UcpDiscoveryProfileSchema.ucp (UcpSchema) was a hand written projection node carrying the withdrawn 2026-01-11 discovery shape: capabilities as a flat array and required, services as a record of single objects, and no payment_handlers. ucp.json#/$defs/base declares every registry as an object keyed by reverse domain name whose values are entity arrays and requires only version; #/$defs/business_schema additionally requires services and payment_handlers and adds supported_versions. The published package therefore rejected the business profile example that the specification publishes (overview/index.md, def=business_schema) on both capabilities and services, while UcpProfileDocumentSchema in the same file, which is derived from the schema, accepted it. Derive the node from business_schema the same way the response envelope is derived, through buildResponseEnvelopeSchema plus the required list and properties of the overlay. Registry items use the per entity response compat shape, as the envelope does. No title is set so the type keeps its property derived name and the UcpSchema export is unchanged. Two properties were being widened past what the schema declares, and both are corrected here. The envelope builder collapsed the map_order $ref to a string, which rejected the second business profile example in the overview (the block with map_order.payment_handlers); map_order is now a record of string arrays per ucp.json#/$defs/map_order, so UcpResponseSchema and UcpCheckoutResponseSchema change on that one line as well. supported_versions was typed as a record of any and accepted a number where the overlay declares a profile URI; its values are now typed as strings. Compile time breaking for consumers of the discovery type: Ucp.capabilities changes from CapabilityDiscovery[] to Record<string, CapabilityResponse[]> and Ucp.services from Record<string, UcpService> to Record<string, ServiceResponse[]>. No exported name is added or removed. Tests: tests/discovery-profile-shape.test.js parses the business profile example from release/2026-08-25 (tests/fixtures/business_profile_2026-08-25.json) through UcpDiscoveryProfileSchema, and pins the record shape of capabilities and services, the presence of payment_handlers, the base and business_schema required sets, the map_order shape, and the supported_versions value type. The two existing tests that used the empty legacy fixture now use the spec shape.
vishkaty
force-pushed
the
fix/discovery-profile-registry-shape
branch
from
September 11, 2026 16:43
b7282b1 to
e099f7a
Compare
This was referenced Sep 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #72. Credit: #19 by @artemii-karkusha reported the divergence first.
What
UcpDiscoveryProfileSchema.ucp(UcpSchema) now models the registries the wayucp.json#/$defs/business_schemadeclares them:services,capabilitiesandpayment_handlersare records keyed by reverse domain name whose values are entity arrays;servicesandpayment_handlersare required,capabilitiesis optional;supported_versions,statusandmap_orderare carried.map_orderis emitted as a record of string arrays perucp.json#/$defs/map_order; the envelope builder collapsed that $ref to a string, soUcpResponseSchemaandUcpCheckoutResponseSchemagain the same one line change and the second business profile example in the overview (themap_order.payment_handlersblock) now parses, after the elision stripping the specification tooling applies to that block.supported_versionswas typed as a record of any and accepted a number where the overlay declares a profile URI; its values are now typed as strings. Before this change the hand written node carried the withdrawn 2026-01-11 shape (capabilities as a required flat array, services as single objects, no payment_handlers) and the package rejected the business profile example that the specification publishes.Breaking (compile-time only):
Ucp.capabilitieschanges fromCapabilityDiscovery[]toRecord<string, CapabilityResponse[]>andUcp.servicesfromRecord<string, UcpService>toRecord<string, ServiceResponse[]>. No exported name is added or removed, and no conformant runtime document was accepted by the old shape.Root cause and fix
writeCompatibilityDiscoverySchemasinscripts/project-current-ucp-schemas.mjswrote theucpnode as a literal. The response envelope in the same function is already derived from#/$defs/basethroughbuildResponseEnvelopeSchema. The newbuildBusinessProfileUcpSchemareuses that derivation and applies the business overlay (itsrequiredlist andsupported_versions).business_schemais the definitionprofile.jsonbinds to what a business hosts at/.well-known/ucp; the platform profile example in the overview parses through the same type sinceplatform_schemahas the same required set. Registry items use the per entity response compat shapes exactly as the envelope does. No title is set, so the type keeps its property derived name and theUcpSchemaexport is unchanged.Generated diff: the
UcpSchemabody, themap_orderline on the two response envelopes, and thesupported_versionsvalue type; quicktype reorders a few unrelated declarations. The set of exported names is identical to main (checked by diffing the sorted export lists).Tests (TDD)
New
tests/discovery-profile-shape.test.jswithtests/fixtures/business_profile_2026-08-25.json, the business profile example fromdocs/specification/overview/index.mdat release/2026-08-25 (theucp:example schema=profile def=business_schemablock, version macro rendered):The two existing tests that used the empty legacy fixture (
capabilities: [], services: {}) now use the specification shape.Written first and watched fail before the fix existed. Measured on the final branch: running these test files against the models generated by unmodified main fails 7 (5 of the new tests plus the 2 updated existing ones); reverting the projector change and regenerating fails the same 7. Per mutant, each new test is killed by its own defect: modelling services as single objects reds the services test (the capabilities test reds under that mutant too, since both read one fixture); dropping payment_handlers reds exactly its own test; reverting only the map_order hunk reds exactly the map_order test; reverting only the supported_versions hunk reds exactly the supported_versions test. Restored: 155 of 155.
Class sweep
Hand written nodes in
writeCompatibilityDiscoverySchemas, each checked against ucp.json at release/2026-08-25:ucpDiscoveryProfile.ucp#/$defs/business_schemacapabilityDiscovery(CapabilityDiscoverySchema)nameUcpSchema; still emitted as its own root so the export stays; candidate for deprecation in a follow upucpService(UcpServiceSchema)ucpDiscoveryProfile.payment.handlersucp.payment_handlers)signingKey(SigningKeySchema)keys[]JWK entriesResidual, stated, and each one over accepts rather than rejecting a conformant document. Registry items use the response compat shapes, so the business level entity requirements are not enforced:
capability.json#/$defs/business_schemarequiresschemaon a business capability entry, andservice.json#/$defs/business_schemarequiresendpointwhen the transport is rest, mcp or a2a. Probed on this branch, the profile example is still accepted with the capabilityschemadeleted and with the rest serviceendpointdeleted. Enforcing either needs a derived business variant per entity, which would add exported types, so it is left for a follow up.supported_versionsvalues are typed as strings; the uri format is not attached, because the constraint injector keys by the containing property set.CI
The workflows here are held behind the approval gate, so I ran them on my fork for this exact commit (e099f7a, the same head this pull request proposes). Both concluded success:
Locally, on this branch:
npm test: 155 passed, 0 failed (main: 147 passed)./generate_models.sh <release/2026-08-25 checkout>twice: byte identical output, and identical to the committedsrc/spec_generated.tsnpm run build:noEmitandnpm run build: exit 0fix:Cross implementation
python-sdk already types these registries as dict of lists (
ucp.py,Base). The Node reference server in samples re-exports the package surface at 0.4.3 but does not use these discovery types, so no consumer change is needed.