Repository navigation
fix(canonical-formats): compare every parameter a format reference declares - #1518
KonstantinMirin wants to merge 1 commit into
Conversation
…clares `core/format-id.json` declares four parameters beyond the `(agent_url, id)` identity — `width`, `height`, `duration_ms` and `pixel_ratio`. Three helpers in `compat_helpers.py` each named three of them in a hand-written tuple, so none of them looked at `pixel_ratio`: - `format_is_supported` accepted a creative that claimed no pixel density for a product requiring 2x, and accepted one claiming a conflicting density; - `formats_are_equivalent` called two references with conflicting densities equal; - `_upgrade_legacy_format_id` discarded the `pixel_ratio` it was handed. Derive the set from the model's fields instead of listing it, so a parameter added to the schema is compared without anyone remembering to name it. A fourth literal would only reset the clock: an independent implementation of the same comparison hit this bug from its own hand-written list of the same three names. `pixel_ratio` keeps one deliberate per-field rule, because the schema gives it one: omitting it selects "the backward-compatible 1x variant", so an absent density reads as 1x rather than as unspecified. A product fixed at 2x therefore refuses a silent request while one fixed at 1x still serves it — without that, closing the gap would introduce the mirror-image false rejection. The new parametrized test reads the parameter list from the bundled schema rather than from the model, so it fails when a helper stops comparing something the spec declares. Closes adcontextprotocol#1517
| if getattr(req, field) != supported_value: | ||
| requested_value = getattr(req, field) | ||
| if requested_value is None: | ||
| requested_value = _IMPLIED_WHEN_ABSENT.get(field) |
There was a problem hiding this comment.
Medium: The implied-1x rule runs on the requested side only. A supported format that omits pixel_ratio hits supported_value is None: continue three lines up and accepts a request stating pixel_ratio: 2. But the schema sentence this fix leans on — "Omit for the backward-compatible 1x variant" — makes an omitted density a value on both sides: a product declaring {display_image, 300x250} is the 1x variant, so matching a 2x request into it is the mirror of the bug you fixed (a 2x asset served against a 1x-declared product). Untested — the suite covers request-omits-vs-supported-2x but not request-2x-vs-supported-omits. If broad-on-density is the intended semantics for the supported side (the width/height template rule), state it; pixel_ratio's omission carries a value where width's does not, so the analogy isn't free.
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — clean, well-tested fix with one non-blocking medium.
The change derives the format-parameter set from FormatId.model_fields and walks it at all three comparison sites, correctly closing the pixel_ratio gap (1x asset served into a 2x slot, conflicting densities compared equal, legacy-upgrade dropping the density). Commit prefix is correctly fix:; no public model shape changes, no import-layering violations, no generated-code hand-edits.
Medium findings
- src/adcp/canonical_formats/compat_helpers.py:191 — Implied-1x rule applied only to the requested side of
format_is_supported, so a supported format omittingpixel_ratiostill accepts a 2x request (mirror of the fixed bug, untested).
Decision path: no critical/high findings (row 1 no); gated_paths false (row 2 no); high_risk false (rows 3/5 no); the single medium is not data-loss/schema/infra (row 4 no); no prior escalation (row 6 no); no no-auto-approve team match (row 7 no); only 1 medium, fewer than 3 (row 8 no) → row 9 approve. Worth a follow-up to apply the implied-1x rule symmetrically, but not blocking.
Medium findings
- src/adcp/canonical_formats/compat_helpers.py:191 — Implied-1x rule applied only to requested side of format_is_supported
Closes #1517.
core/format-id.jsondeclares four parameters beyond the(agent_url, id)identity —width,height,duration_msandpixel_ratio. Three helpers inadcp/canonical_formats/compat_helpers.pyeach named three of them in a hand-writtentuple, so none of them ever looked at
pixel_ratio.What was wrong
Measured on
d5195a952, before this change:The first line is the one with a delivery consequence: a seller publishing a format that
requires a 2x asset accepts a creative that never claims one, so a 1x image is served into
a 2x slot. The directional rule
format_is_supportedalready implements correctly for theother three parameters — if the supported side fixes a parameter, the request must state and
match it — simply never ran for this one.
What changed
The parameter set is now derived:
set(FormatId.model_fields) - {"agent_url", "id"},read once into
_PARAMETER_FIELDSand walked by all three sites.Deriving rather than appending a fourth name is the point of the change. An independent
server-side implementation of this same comparison hit the identical bug from its own
hand-written list of the same three names — two implementations, one list each, the same
parameter missing from both. A derived set means the next parameter added to
core/format-id.jsonis compared without anyone remembering to name it.One rule stays per-field, because the schema gives it one. On
pixel_ratio:So an absent density is a value, not silence — unlike an absent
width, which means"template, unparameterized".
_IMPLIED_WHEN_ABSENTcarries that: a product fixed at 2xrefuses a request that says nothing, while one fixed at 1x still serves it. Without it,
closing the gap would have introduced the mirror-image false rejection, refusing every
buyer who uses the omitted form against a seller who spells
pixel_ratio: 1explicitly.Relationship to #1364
#1364 was a different defect in
format_is_supported— it refused theFormatReferenceStructuredObjectthat generated model fields carry — and its fix landed on2026-10-04. These gaps survived it untouched: the input coercion changed, the comparison
body did not.
Tests
tests/test_canonical_formats_compatibility.pygains five tests. Four of them are themeasured cases above; the fifth is the guard that would have caught the original bug:
Its parameter list comes from
get_named_schema_document("core/format-id.json"), not fromthe model, so it fails when a helper stops comparing something the spec declares rather
than only when helper and model disagree. A companion test asserts the conflict table
covers every parameter the schema declares, so a new one cannot slip past the parametrize.
Red before the change, on the commit this branch is based on:
Green after:
test_format_is_supported_accepts_the_omitted_form_of_a_1x_productpasses on both sides —it passed vacuously before (the field was ignored), and it is here to hold the 1x rule
above in place, not as evidence of the fix.
Checks run locally
ruff check src/— cleanblack— clean on both changed filesmypy src/adcp/— no issues in 1534 source filesmypy --strict tests/type_checks/— no issues in 73 source filesscripts/run_pyright_type_checks.py— 0 errors over 62 fixturesscripts/check_type_ignore_contract.py— cleanmake validate-generated— generated types up to datepytest -k 'format or canonical or compat or projection or creative'— 2953 passed, 310skipped, 0 failed
Noted, deliberately not included
_looks_like_legacy_format_tupleinadcp/types/canonical_creative.pycarries the samethree-name list as a key-subset test, so
sanitize_canonical_schemaleavesagent_urlinpropertiesandrequiredfor a node that also carriespixel_ratio. Its only caller ispydantic schema generation, where the node always has a class-name title containing
"format" and the first branch matches — so there is a latent inconsistency but no
reachable consequence I could demonstrate, and no behavioral test to pin a change by. It is
described in #1517 for a separate look rather than patched speculatively here.