Skip to content

feat: Introduce anyOf constraint with payments example - #757

Merged
amithanda merged 4 commits into
mainfrom
raginpirate/anyof-constraint
Aug 22, 2026
Merged

feat: Introduce anyOf constraint with payments example#757
amithanda merged 4 commits into
mainfrom
raginpirate/anyof-constraint

Conversation

@raginpirate

Copy link
Copy Markdown
Member

Description

Resolving our open follow-up to #655, introducing the anyOf constraint to the base UCP constraints grammar.
Added a simple example using it with payments, and #424 is stacked on top of this to extend the usage of constraints across payments normatively.

Category (Required)

Please select one or more categories that apply to this change.

  • Core Protocol: Changes to the base communication layer, global context, or breaking refactors. (Requires Technical Council approval)
  • Governance/Contributing: Updates to GOVERNANCE.md, CONTRIBUTING.md, or CODEOWNERS. (Requires Governance Council approval)
  • Capability: New schemas (Discovery, Cart, etc.) or extensions. (Requires Maintainer approval)
  • Documentation: Updates to README, or documentations regarding schema or capabilities. (Requires Maintainer approval)
  • Infrastructure: CI/CD, Linters, or build scripts. (Requires DevOps Maintainer approval)
  • Maintenance: Version bumps, lockfile updates, or minor bug fixes. (Requires DevOps Maintainer approval)
  • SDK: Language-specific SDK updates and releases. (Requires DevOps Maintainer approval)
  • Samples / Conformance: Maintaining samples and the conformance suite. (Requires Maintainer approval)
  • UCP Schema: Changes to the ucp-schema tool (resolver, linter, validator). (Requires Maintainer approval)
  • Community Health (.github): Updates to templates, workflows, or org-level configs. (Requires DevOps Maintainer approval)

Checklist

  • I have followed the Contributing Guide (including Conventional Commits title requirements and ! for breaking changes).
  • I have updated the documentation (if applicable).
  • 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.
  • I have regenerated Python Pydantic models by running generate_models.sh under python_sdk.

@igrigorik igrigorik left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Note: bundling main.py fix for self-root schema refs surfaced via this PR 899935e

Comment thread source/schemas/common/types/constraint_expression.json
Comment thread main.py Outdated
raginpirate and others added 4 commits August 21, 2026 19:27
* docs: complete abridged payment instrument examples

Both examples elided id, handler_id, and type behind a "more required
fields" comment, and one also omitted credential.type. They validated
only because shipped ucp-schema never applied the self-root allOf
requirements in selected_payment_instrument, so payment.instruments[]
was never checked against payment_instrument's required members.

Showing the fields costs one line each, keeps both examples validated
once that resolver bug is fixed, and removes a comment that promised
required fields while showing none.

* feat: require non-empty anyOf branches in the constraint grammar

An empty Object Constraint is a valid no-op at every other position, but
an empty anyOf branch is satisfied by every object, so it neutralizes
the alternation rather than doing nothing. The grammar should not admit
a branch that silently disables the alternation it belongs to.

A minProperties sibling on the recursive $ref is legal 2020-12
composition: siblings to $ref apply alongside the referenced schema, so
the branch stays an ordinary Object Constraint while gaining the
non-empty requirement, at every nesting depth.

Mirrors the constraint into both grammar tables, since the schema and
its companion prose are one unit.

* docs: discriminate the anyOf credential example

The undiscriminated form accepts nonsense: a credential declaring any
card_number_type at all satisfies the cvc branch as long as it carries a
cvc, so the example teaches an idiom that does not enforce what the
surrounding prose claims. Pinning card_number_type with const in each
branch, and naming it in that branch's required, makes a branch match
only the shape it describes.

Documents the consequence that pinning creates: because every branch
pins the discriminator, the branch set also closes the accepted values,
so a dpan credential valid under card_credential.json satisfies neither
branch. That closure is the rule a per-branch conditional cannot
express, and it is load-bearing enough to state.

Replaces the "would restate the path" justification, which understated
the case. Request Constraints conjoin, so two separately targeted
constraints would require both cvc and cryptogram; the real alternative
is discriminating in the path filter, which puts conditional logic in
the selector.

Adds the two grammar mechanics the example relies on next to the
composition rules rather than in the Business guidelines: branches are
alternatives rather than a partition, and properties asserts nothing
about an absent member. Both are consequences of how the grammar
composes, not conformance requirements, so they carry no BCP 14
keywords and belong with the grammar they describe.
A bare "$ref": "#" names the schema being rendered and carries no
filename of its own, so create_link derived an empty anchor and emitted
a broken "<page>/##" link. The docs build's link check fails on it,
which is why build_and_verify_main has been red on this branch since
the first push.

Recursive references cannot be inlined -- ucp-schema preserves them
even under --bundle, and the docs macro deliberately resolves without
bundling so that $refs survive for hyperlink generation. The docs layer
is therefore the only place that can give a self-root ref a name.
Resolve "#" against the enclosing schema's own $id at each of the three
sites that turn a $ref into a link: direct, array items, and oneOf
branches.

The array-items path is the one the constraint grammar's anyOf trips
today. The oneOf path has the identical defect -- a named property with
oneOf: [{"$ref": "#"}, ...] renders the same broken anchor -- and is
fixed alongside it rather than left to be rediscovered.
@raginpirate
raginpirate force-pushed the raginpirate/anyof-constraint branch from 899935e to e8da3e8 Compare August 21, 2026 23:34
@amithanda
amithanda merged commit 3c4b468 into main Aug 22, 2026
19 checks passed
@amithanda
amithanda deleted the raginpirate/anyof-constraint branch August 22, 2026 02:22
@github-actions github-actions Bot added the enhancement New feature or request label Aug 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:payments enhancement New feature or request TC review Ready for TC review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants