spec(schemas): add discriminator hints to const-property oneOf unions (adcp#3917)#3928
Merged
spec(schemas): add discriminator hints to const-property oneOf unions (adcp#3917)#3928
Conversation
… (adcp#3917)
Adds `discriminator: { propertyName }` to 17 oneOf unions where every
variant already declares the same required property as a const with
distinct string values. Non-breaking: validators that don't recognize
the OpenAPI keyword ignore it, but codegen targets that do (msgspec,
openapi-typescript, datamodel-code-generator) now emit a properly
narrowed union without per-variant casts.
Skipped:
- Boolean discriminators (Ajv requires unique strings) — needs a
separate fix to convert the boolean to an enum
- Ref-only variants in core/format.json and core/pricing-option.json —
Ajv discriminator support doesn't follow $refs; these need either
inlining or a different keyword strategy
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…uth drift Per protocol-expert review of #3928: when `discriminator.propertyName=X` is declared on a oneOf, every non-ref variant must declare `properties.X` as a required `const` with distinct values across variants. Otherwise the discriminator is just a codegen hint that validators ignore, and a future schema author can drift the const off without any signal. Tightens scripts/audit-oneof.mjs to enforce this invariant via the existing baseline-diff CI gate. Also corrects the changeset count (15 hoists / 16 unions, not 17). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 3, 2026
bokelley
added a commit
that referenced
this pull request
May 3, 2026
…rmat inner-asset oneOf (adcp#3917) (#3934) Two of the items deferred from #3928 turn out to be safe with a small adjustment: - core/pricing-option.json #/oneOf — discriminator.propertyName=pricing_model. Ajv resolves the cross-file $refs to pricing-options/*-option.json cleanly when all schemas are pre-registered via addSchema(); the earlier deferral was based on a faulty isolated-compile test. - core/format.json inner oneOf at #/properties/assets/items/oneOf/14/properties/assets/items — discriminator.propertyName=asset_type. Ajv's discriminator support does not follow allOf to find the inherited required, so each of the 12 inner variants gains a direct required:["asset_type"]. The const was already declared inline. Outer 15-variant oneOf and the two boolean-discriminator unions remain deferred — they need shape changes, not a free hoist. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 3, 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.
Summary
Step 2 of the discriminator audit plan from #3917. Adds
discriminator: { propertyName }to 16oneOfunions instatic/schemas/source/whose variants already declare the same required property as aconstwith distinct string values. Also tightens the audit walker to enforce that anydiscriminator.propertyName=Xis backed by every non-ref variant declaringproperties.Xas required const — preventing a two-sources-of-truth drift where the keyword sits without the structural narrowing.Non-breaking — the OpenAPI
discriminatorkeyword is ignored by JSON Schema 2020-12 validators that don't recognize it. Existingconst-property narrowing remains the source of truth. Codegen targets that respect the keyword (msgspec, openapi-typescript, datamodel-code-generator) now emit a properly-narrowed union without per-variant casts.Affected schemas (16 unions, 15 hoists across 12 files)
adagents.json(authorization_type)compliance/comply-test-controller-response.json(attestation_mode)content-standards/artifact.json(type,method)core/activation-key.json(type)core/creative-item.json(asset_kind)core/deployment.json(type)core/destination.json(type)core/optimization-goal.json(kind× 3)core/requirements/catalog-field-binding.json(kind× 2)core/signal-pricing.json(model)creative/preview-creative-response.json(response_type)creative/preview-render.json(output_format)Walker tightening
scripts/audit-oneof.mjsnow treatsdiscriminator.propertyName=Xas ✗ if any non-ref variant fails to declareproperties.Xas required const, or if two variants share the same const value. Nested-union and pure-ref variants are trusted (their target may carry the const further down). This guards the failure mode the protocol expert flagged: a future author could adddiscriminator.propertyName=Xwithout the const, weakening validation for non-OpenAPI consumers.Deferred to follow-ups
get-adcp-capabilities-response.jsonsupported,update-content-standards-response.jsonsuccess) — Ajv requires unique-string valuescore/format.jsonassets/items→asset_type,core/pricing-option.json→pricing_model) — Ajv discriminator support doesn't follow cross-file$ref. (Within-file#/definitionsrefs work fine and are already deployed incatalog-field-binding.json.)Test plan
node scripts/audit-oneof.mjs— 36 ✓ unchanged (these were already classified ✓ via const)discriminator.propertyName="wrong_key"oncore/activation-key.jsoncorrectly trips ✗ with a clear "missing const+required" messagecore/assets/asset-union.jsonandcore/offering-asset-group.json(which use nested-union ref variants) still pass — the invariant trusts nested-union targetsnpm run test:json-schema(257/0)npm run test:schemas(7/0)npm run test:composed(26/0)npm run test:oneof-discriminators— passes against committed baseline🤖 Generated with Claude Code