You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
AccountReference = { account_id } | { brand, operator, sandbox? } has no spec-level discriminator. Every adapter we built (and the four reviewer agents) collapsed the union via (ref as { brand?: { domain?: string } }) and silently dropped the account_id arm. Real adopters who wire sync_accounts will hit the gap because buyers reference accounts by account_id post-sync.
This isn't an AccountReference-only issue. Other unions in AdCP 3.x with the same risk:
Every *Success | *Error response union (most of AdCP 3.x)
AsyncResponseFor<T> family
SignalID ✓ has source
Some unions have discriminators. Many don't. The ones without push the narrowing burden onto every adopter in every codegen target — TS, Python, Go, Java — and every adopter writes the same buggy cast.
Proposed fix
Audit pass: for every oneOf in schemas/cache/latest/, classify:
✓ Has a stable discriminator key (kind, source, response_type, asset_type, output_format, type).
⚠ Doesn't have one but variants have a structurally distinguishing required field (e.g. account_id vs brand on AccountReference).
✗ Variants overlap structurally and need a discriminator added (the dangerous case).
Add discriminators to every ⚠ and ✗ union. For AccountReference specifically: add kind: 'account_id' | 'identity' (or similar) so TS/Python/Go can narrow without 'account_id' in ref archaeology. JSON Schema Draft 2020-12 discriminator keyword OR a sentinel propertyName constant per branch.
Update the JSON Schema to declare discriminator: { propertyName: 'kind' } on every oneOf. Codegen targets that support discriminators (jsts with the right config, msgspec, openapi-generator) emit narrower types.
CI assertion
Schema-walker on every oneOf that asserts:
Either discriminator: { propertyName: ... } is set, OR
Every variant declares the same properties.<key>.const value (and they're distinct across variants).
Output on failure: oneOf at /core/account-ref.json#/ has no discriminator and no shared const-property — add { kind: 'account_id' | 'identity' } or equivalent.
This is a single-pass schema walker. ~50 lines of TS / Python in the spec repo's CI. Catches every regression on every spec PR going forward.
Why on adcp not adcp-client
The audit is on the spec; codegen targets are downstream. Filing on adcontextprotocol/adcp so the discriminator fix lands at the source and propagates to adcp-client (TS), adcp-py, etc.
Acceptance
Audit table published as a PR description listing every oneOf and its current discriminator status.
AccountReference gains kind discriminator; codegen produces a properly-narrowed union in TS.
CI assertion in adcp spec repo fails any PR that adds a oneOf without a discriminator.
adcp-client's generated TS for AccountReference makes 'account_id' in ref unnecessary.
Summary
AccountReference = { account_id } | { brand, operator, sandbox? }has no spec-level discriminator. Every adapter we built (and the four reviewer agents) collapsed the union via(ref as { brand?: { domain?: string } })and silently dropped theaccount_idarm. Real adopters who wiresync_accountswill hit the gap because buyers reference accounts byaccount_idpost-sync.This isn't an
AccountReference-only issue. Other unions in AdCP 3.x with the same risk:BuildCreativeReturn(CreativeManifest | CreativeManifest[] | BuildCreativeSuccess | BuildCreativeMultiSuccess)PreviewCreativeResponse(single | batch | variant) ✓ hasresponse_typeMediaBuyDeliveryNotificationvariantsActivationKey✓ hastypeAssetVariant✓ hasasset_typeFormat.renders[]items (dimensions vs parameters_from_format_id) — fixed via codegen tightening (feat(dashboard): escalation visibility and invoice draft-confirm flow #1325)*Success | *Errorresponse union (most of AdCP 3.x)AsyncResponseFor<T>familySignalID✓ hassourceSome unions have discriminators. Many don't. The ones without push the narrowing burden onto every adopter in every codegen target — TS, Python, Go, Java — and every adopter writes the same buggy cast.
Proposed fix
oneOfinschemas/cache/latest/, classify:kind,source,response_type,asset_type,output_format,type).account_idvsbrandonAccountReference).AccountReferencespecifically: addkind: 'account_id' | 'identity'(or similar) so TS/Python/Go can narrow without'account_id' in refarchaeology. JSON Schema Draft 2020-12discriminatorkeyword OR a sentinelpropertyNameconstant per branch.discriminator: { propertyName: 'kind' }on everyoneOf. Codegen targets that support discriminators (jsts with the right config, msgspec, openapi-generator) emit narrower types.CI assertion
Schema-walker on every
oneOfthat asserts:discriminator: { propertyName: ... }is set, ORproperties.<key>.constvalue (and they're distinct across variants).Output on failure:
oneOf at /core/account-ref.json#/ has no discriminator and no shared const-property — add { kind: 'account_id' | 'identity' } or equivalent.This is a single-pass schema walker. ~50 lines of TS / Python in the spec repo's CI. Catches every regression on every spec PR going forward.
Why on adcp not adcp-client
The audit is on the spec; codegen targets are downstream. Filing on
adcontextprotocol/adcpso the discriminator fix lands at the source and propagates to adcp-client (TS), adcp-py, etc.Acceptance
oneOfand its current discriminator status.AccountReferencegainskinddiscriminator; codegen produces a properly-narrowed union in TS.oneOfwithout a discriminator.AccountReferencemakes'account_id' in refunnecessary.Refs
hello_*_adapter_*.tsexamples)src/lib/server/decisioning/account.tsAccountStore.resolveJSDoc — documents the canonical narrowing pattern that today requires the cast🤖 Generated with Claude Code