Skip to content

fix: keep additional properties on open schemas - #84

Merged
damaz91 merged 2 commits into
Universal-Commerce-Protocol:mainfrom
sakinaroufid:fix/retain-additional-properties
Oct 5, 2026
Merged

damaz91 merged 2 commits into
Universal-Commerce-Protocol:mainfrom
sakinaroufid:fix/retain-additional-properties

Conversation

@sakinaroufid

Copy link
Copy Markdown
Contributor

Description

Generated objects are bare z.objects, which strip unknown keys, so objects the spec declares open (additionalProperties: true) lose data on parse. CheckoutCompleteRequestSchema.parse(body) drops payment.instruments[].credential.token (the credential is the open payment_credential.json) and ap2, and checkouts and carts lose extension fields such as fulfillment and discounts. The getting-started guide on ucp.dev works with the parsed .data, so code that follows it is affected.

  • Commit 1: a new generation step, scripts/retain-additional-properties.mjs, appends .catchall(z.any()) to a generated object when every source schema with its property set is open, following $ref and allOf across the authored, projected and discovery trees. 71 objects gain it. Six that also stand for a closed or undeclared schema are left as generated, and the step reports them.
  • Commit 2: GetProductResponseSchema dropped available and exists. quicktype's allOf intersection of two arrays keeps only one side's items, so detail_product's redeclared options[].values[] lost to product.json's. The projection now moves such a redeclaration into a trailing allOf branch, the side quicktype keeps.

Compatibility: on those 71 objects, parse now keeps unknown keys, and their z.infer types gain [k: string]: any, so an unmodelled property is no longer a type error. Closed schemas still strip.

Category (Required)

  • Core Protocol
  • Governance/Contributing
  • Capability
  • Documentation
  • Infrastructure
  • Maintenance
  • SDK
  • Samples / Conformance
  • UCP Schema
  • Community Health (.github)

Related Issues

None.

Checklist

  • I have followed the Contributing Guide (including Conventional Commits title requirements and ! for breaking changes).
  • I have updated the documentation (if applicable). (n/a)
  • My changes pass all local linting and formatting checks.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • (For Core/Capability) I have included/updated the relevant JSON schemas. (n/a)
  • I have regenerated Python Pydantic models. (n/a; Zod models regenerated against release/2026-08-25 and match byte for byte)

Screenshots / Logs (if applicable)

credential.token after parse    before: dropped    after: kept
spec example payloads losing data on parse: 57 before, 6 after (none involve payment credentials)
npm test: 282 pass, 0 fail      npm run build: OK

quicktype emits every object as a bare z.object, which strips unknown keys
on parse, so objects the spec declares open (additionalProperties: true)
lost data. A complete checkout request dropped
payment.instruments[].credential.token, and checkouts and carts parsed with
the base schemas dropped their extension fields.

A new generation step, scripts/retain-additional-properties.mjs, appends
.catchall(z.any()) to a generated object when every source schema with its
property set is open, following $ref and allOf. It looks at the authored,
projected and discovery trees together, because one generated object can
stand for schemas from more than one of them. Objects that also stand for a
closed schema, or for one that does not declare itself open, are left as
generated, and the step reports them.

71 objects gain .catchall(z.any()). Six are left as generated:
A2ASchema, PriceClassSchema, PriceRangeSchema, ConstraintsElementSchema,
FulfillmentCreateRequestSchema and FulfillmentUpdateRequestSchema. The
pretest step now compiles extensions.ts as well, so the tests can check that
the Extended* schemas keep unknown keys.
catalog_lookup.json's detail_product composes product.json and redeclares
options so that options[].values[] are detail_option_value.json, which adds
available and exists. The generated GetProductResponseSchema still used the
plain option value, so both fields were stripped on parse and
DetailOptionValueSchema went unused.

The cause is in quicktype: when it intersects two arrays for allOf, it drops
the items of the first array it meets (updateArrayItemTypes in
quicktype-core's ResolveIntersections), so the base's items won. The
projection now moves an array property that redeclares an inherited array
with different items into a trailing allOf branch, which is the array whose
items quicktype keeps. detail_product is the only such case in the pinned
spec.

ProductClassSchema.options now uses a new OptionSchema whose values are
DetailOptionValueSchema. No other generated schema changes.
@damaz91 damaz91 added status:needs-triage Signal that the PR is ready for human triage status:under-review and removed status:needs-triage Signal that the PR is ready for human triage labels Oct 3, 2026
@damaz91
damaz91 merged commit 002cc6d into Universal-Commerce-Protocol:main Oct 5, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants