Repository navigation
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
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
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — subsequent pass, net improvement over prior review.
This PR derives the format parameter set from the model rather than a hand-written list, so pixel_ratio is now correctly compared in formats_are_equivalent, format_is_supported, and the legacy-size upgrade path. A schema-driven test guard fails when a new parameter goes uncovered — good regression protection.
Medium findings (1, non-blocking):
- src/adcp/canonical_formats/compat_helpers.py:191 — Implied-1x rule applied only to the requested side of
format_is_supported; a supported product omittingpixel_ratio(a 1x product per schema) fail-opens on a 2x request.
This single prior Medium persists but no new Critical/High surfaced. With only 1 medium finding, no blocking issues, no high-risk/gated-path/team-gate triggers, this falls through to row 9. Worth a follow-up to apply the implied-1x rule symmetrically on both sides, but not merge-blocking.
Medium findings
- src/adcp/canonical_formats/compat_helpers.py:191 — Implied-1x rule applied only to requested side of format_is_supported
…quivalent too `formats_are_equivalent` still treated an omitted `pixel_ratio` as unspecified, so it called 300x250 and 300x250 at 2x the same format while `format_is_supported` refused one for the other. An omitted width or duration names the template, a family covering every variant, so "unspecified" is right for those. An omitted density on a sized reference names one variant — the 1x render of a 300x250 image, not the 600x500 one — so read it as 1x here as well. A size-less template still has no density and stays broad. The schema sanitizer's legacy-tuple shape check listed the reference's properties by hand and missed `pixel_ratio`; derive the set from the model. Refs #1518, #1517 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bokelley
left a comment
There was a problem hiding this comment.
Approving. Independently reviewed at 7e6e706. The parameter set derived from FormatId.model_fields is exactly duration_ms/height/pixel_ratio/width. The new tests fail against main's helper (3 of 23). 281 format tests and 111 public-API/layering/canonical tests pass, and ruff and mypy are clean. Int vs float and implied-1.0 comparisons behave correctly, and the legacy upgrade keeps density.
The non-blocking items will land in a follow-up PR rather than here:
- the implied-1x rule on the supported side of
format_is_supported(only when that side declares width/height, so size-less templates keep accepting any density) - whether
formats_are_equivalentshould follow the same rule, since the new test currently asserts omitted ≡ 2x - the hard-coded parameter names in
_looks_like_legacy_format_tuple(canonical_creative.py:549)
|
@bokelley auto-merge can't complete here. Every required check passes and every thread is resolved, but the |
…orted side too `format_is_supported` applied the implied-1x rule only to the requested reference. A supported 300x250 that omits `pixel_ratio` was skipped as "unfixed", so it accepted a 2x creative — the mirror image of the gap #1518 closed for a 2x product receiving a request that omits the field. `core/format-id.json` describes the omitted field as "the backward-compatible 1x variant" without naming a side, so read it the same way on both. The reading applies only to a dimensioned reference: `pixel_ratio` "Requires `width` and `height`", so a bare `display_image` template names no variant to be 1x of and stays broad. A legacy `display_300x250` upgrades to a dimensioned reference and is therefore the 1x variant. `formats_are_equivalent` keeps its documented loose contract (an omitted parameter is unspecified on either side), which is already symmetric. Refs #1518, #1517 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…quivalent too `formats_are_equivalent` still treated an omitted `pixel_ratio` as unspecified, so it called 300x250 and 300x250 at 2x the same format while `format_is_supported` refused one for the other. An omitted width or duration names the template, a family covering every variant, so "unspecified" is right for those. An omitted density on a sized reference names one variant — the 1x render of a 300x250 image, not the 600x500 one — so read it as 1x here as well. A size-less template still has no density and stays broad. The schema sanitizer's legacy-tuple shape check listed the reference's properties by hand and missed `pixel_ratio`; derive the set from the model. Refs #1518, #1517 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…orted side too (#1553) * fix(canonical-formats): read an omitted pixel_ratio as 1x on the supported side too `format_is_supported` applied the implied-1x rule only to the requested reference. A supported 300x250 that omits `pixel_ratio` was skipped as "unfixed", so it accepted a 2x creative — the mirror image of the gap #1518 closed for a 2x product receiving a request that omits the field. `core/format-id.json` describes the omitted field as "the backward-compatible 1x variant" without naming a side, so read it the same way on both. The reading applies only to a dimensioned reference: `pixel_ratio` "Requires `width` and `height`", so a bare `display_image` template names no variant to be 1x of and stays broad. A legacy `display_300x250` upgrades to a dimensioned reference and is therefore the 1x variant. `formats_are_equivalent` keeps its documented loose contract (an omitted parameter is unspecified on either side), which is already symmetric. Refs #1518, #1517 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(canonical-formats): apply implied-1x pixel_ratio in formats_are_equivalent too `formats_are_equivalent` still treated an omitted `pixel_ratio` as unspecified, so it called 300x250 and 300x250 at 2x the same format while `format_is_supported` refused one for the other. An omitted width or duration names the template, a family covering every variant, so "unspecified" is right for those. An omitted density on a sized reference names one variant — the 1x render of a 300x250 image, not the 600x500 one — so read it as 1x here as well. A size-less template still has no density and stays broad. The schema sanitizer's legacy-tuple shape check listed the reference's properties by hand and missed `pixel_ratio`; derive the set from the model. Refs #1518, #1517 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.