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
{{ message }}
Repository navigation
bug(types): optional const:true properties generate Literal[True] = True defaults, so TargetingOverlaySupport promises every postal system #1347
Codegen turns an optional schema property declared as {"const": true} into Literal[True] = True. A model then reports every such field as set to true, including ones the payload never sent, and model_dump(exclude_none=True) emits them.
The worst case is core/positive-postal-area-support.json. Any geo_postal_areas declaration in TargetingOverlaySupport dumps with every deprecated postal flag set. On Product.overlay_support, which AdCP 3.2 defines as a binding promise, that tells buyers the product supports every postal system.
Expected: {'geo_postal_areas': {'US': ['zip']}}. exclude_unset=True gives the right answer, but the SDK's own serialization paths (e.g. the MCP/A2A dispatch model_dump(mode="json", exclude_none=True)) don't use it.
Root cause
In core/positive-postal-area-support.json (3.2.1), the second allOf arm narrows the deprecated legacy flags to {"const": true}. Each one is optional; none is in required:
For an optional property, const restricts the value if present. It doesn't supply a default.
Other places with the same pattern
grep -rn "Literal\[True\].*= True" adcp/types/generated_poc finds 33 fields. Besides the 11 postal flags, there is replayed: Literal[True] = True on response variants in list_products_response.py, request_proposals_response.py, buy_products_response.py, accept_proposal_response.py, control_media_buy_response.py, media_buy_commitment_response.py, refine_proposals_response.py and decline_proposals_response.py. In 3.2.1 list-products-response.json, replayed is {"type": "boolean", "const": true} and is not required. I haven't checked whether union resolution can put a non-replayed response on those variants, but the generated default doesn't match the schema either way.
Suggested fix
For an optional property with const, generate Literal[<const>] | None = None, or leave it required with no default. Only a property listed in required should come out as Literal[...] with no | None. A codegen regression test that flags any = <const> default on a property not in required would catch this class of bug.
Downstream workaround
The embedded sales agent (scope3data/embedded-sales-agent#578) works around it in two places:
It stores the seller's overlay_support as schema-validated JSON instead of round-tripping it through the model.
get_products re-dumps each product's overlay_support with exclude_unset=True (_with_declared_overlay_support in core/platforms/_delegate.py).
Classification: Bug Bucket(s): validation (no exact matching label — gap noted) Status: ready-to-implement
What the experts said:
ad-tech-protocol-expert: Bug confirmed; const: true without required is a restriction on presence, not a default — Literal[True] | None = None is the correct correction per JSON Schema; minor semver bump warranted; watch regex for the compact Annotated[Literal[True], Field(deprecated=True)] form vs. the multi-line canceled/unchanged shape.
code-reviewer: Fix location (post_generate_fixes.py) is correct; replayed (22 occurrences across 8 response files) is low-risk and follows the established pattern; PositivePostalAreaSupport postal flags are moderate-risk — the class is explicitly "positive" and may be designed to assert all flags — confirm schema intent before widening; regression test feasible in tests/test_import_layering.py.
My take: Both experts agree the replayed fix is unambiguous. The postal-flag scope carries a legitimate schema-intent question from the code reviewer, but the issue author (MEMBER) filed this as a bug and the reproduction confirms false capability advertisement on a binding Product.overlay_support field. Treat the postal flags as bug-scope and note the schema-intent question in the PR.
Implementation brief:
Scope A — replayed (22 fields, 8 files). Add fix_replayed_literal_defaults() to scripts/post_generate_fixes.py, modelled on fix_canceled_literal_defaults(). Target: media_buy/{list,request_proposals,buy_products,accept_proposal,control_media_buy,media_buy_commitment,refine_proposals,decline_proposals}_response.py. Widen replayed: Literal[True] = True → Literal[True] | None = None. The replayed field is set by server-originated deserialization, so no caller constructs it bare.
Scope B — postal flags (11 fields, core/positive_postal_area_support.py). Add fix_postal_literal_defaults() to post_generate_fixes.py. Handle the compact annotation form: us_zip: Annotated[Literal[True], Field(deprecated=True)] = True → Literal[True] | None = True. Note in PR: PositivePostalAreaSupport semantics should be confirmed — if the "positive" variant is intended to assert all 11 legacy flags unconditionally, the discriminated union should enforce that via required, not defaults. Either way, phantom defaults on an optional field are wrong.
Regression test. In tests/test_import_layering.py (or a new tests/test_codegen_invariants.py), parse generated_poc/ with the ast module, collect all field assignments matching = True or = False on classes with a Literal[True]/Literal[False] annotation, and assert the list is empty after post_generate_fixes.py runs. This catches any future const-default misfire.
Required checks:pytest tests/ · mypy src/ · ruff check . / black --check . · make validate-generated. No generated_poc/ edits directly — only post_generate_fixes.py changes which take effect on the next make regenerate-schemas.
Non-breaking rationale: Callers reading .us_zip (etc.) on a freshly-constructed PositivePostalAreaSupport() will get None instead of True; that code was relying on phantom data. No wire input changes — Literal[True] validation on received values is preserved. Treat as minor-version change per the protocol expert's assessment.
Likely files:scripts/post_generate_fixes.py (add two new functions, wire into main()), tests/test_codegen_invariants.py (new regression test). Regenerated generated_poc/ files are also updated but those are auto-generated output.
Activity
bokelley commentedon Oct 3, 2026
Triage
Classification: Bug
Bucket(s): validation (no exact matching label — gap noted)
Status: ready-to-implement
What the experts said:
const: truewithoutrequiredis a restriction on presence, not a default —Literal[True] | None = Noneis the correct correction per JSON Schema; minor semver bump warranted; watch regex for the compactAnnotated[Literal[True], Field(deprecated=True)]form vs. the multi-linecanceled/unchangedshape.post_generate_fixes.py) is correct;replayed(22 occurrences across 8 response files) is low-risk and follows the established pattern;PositivePostalAreaSupportpostal flags are moderate-risk — the class is explicitly "positive" and may be designed to assert all flags — confirm schema intent before widening; regression test feasible intests/test_import_layering.py.My take: Both experts agree the
replayedfix is unambiguous. The postal-flag scope carries a legitimate schema-intent question from the code reviewer, but the issue author (MEMBER) filed this as a bug and the reproduction confirms false capability advertisement on a bindingProduct.overlay_supportfield. Treat the postal flags as bug-scope and note the schema-intent question in the PR.Implementation brief:
Scope A —
replayed(22 fields, 8 files). Addfix_replayed_literal_defaults()toscripts/post_generate_fixes.py, modelled onfix_canceled_literal_defaults(). Target:media_buy/{list,request_proposals,buy_products,accept_proposal,control_media_buy,media_buy_commitment,refine_proposals,decline_proposals}_response.py. Widenreplayed: Literal[True] = True→Literal[True] | None = None. Thereplayedfield is set by server-originated deserialization, so no caller constructs it bare.Scope B — postal flags (11 fields,
core/positive_postal_area_support.py). Addfix_postal_literal_defaults()topost_generate_fixes.py. Handle the compact annotation form:us_zip: Annotated[Literal[True], Field(deprecated=True)] = True→Literal[True] | None = True. Note in PR:PositivePostalAreaSupportsemantics should be confirmed — if the "positive" variant is intended to assert all 11 legacy flags unconditionally, the discriminated union should enforce that viarequired, not defaults. Either way, phantom defaults on an optional field are wrong.Regression test. In
tests/test_import_layering.py(or a newtests/test_codegen_invariants.py), parsegenerated_poc/with theastmodule, collect all field assignments matching= Trueor= Falseon classes with aLiteral[True]/Literal[False]annotation, and assert the list is empty afterpost_generate_fixes.pyruns. This catches any futureconst-default misfire.Required checks:
pytest tests/·mypy src/·ruff check ./black --check .·make validate-generated. Nogenerated_poc/edits directly — onlypost_generate_fixes.pychanges which take effect on the nextmake regenerate-schemas.Non-breaking rationale: Callers reading
.us_zip(etc.) on a freshly-constructedPositivePostalAreaSupport()will getNoneinstead ofTrue; that code was relying on phantom data. No wire input changes —Literal[True]validation on received values is preserved. Treat as minor-version change per the protocol expert's assessment.Likely files:
scripts/post_generate_fixes.py(add two new functions, wire intomain()),tests/test_codegen_invariants.py(new regression test). Regeneratedgenerated_poc/files are also updated but those are auto-generated output.Triaged by Claude Code. Session: https://claude.ai/code/session_01PvmQLEpQS4PP2vW2R4sXnw
Generated by Claude Code
fix(types)!: preserve omission of optional boolean constants (adconte…
Merge upstream/main (91b0904) into fix/pipeline-detects-its-own-stale…