Skip to content

Commit 12cb5cf

Browse files
committed
fix(canonical-formats): honor seller Retina precedence
1 parent b9c3024 commit 12cb5cf

2 files changed

Lines changed: 75 additions & 8 deletions

File tree

src/adcp/canonical_formats/v1_to_v2.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
:class:`CanonicalProjectionReference` carrying ``kind``,
1919
``asset_source``, and ``slots_override[]``. Highest priority on
2020
the v1→v2 path. Registry-published ``parameters`` for a matching
21-
glob still fill in anything the seller didn't restate.
21+
glob still fill neutral parameters the seller didn't restate, but
22+
registry slot and pixel-density defaults never merge into the
23+
seller-authored declaration.
2224
2. **Registry glob match** (registry step 3). Look up
2325
``v1_format.format_id.id`` in the bundled registry's
2426
``format_id_glob`` entries. The bundled 3.1.10 registry includes
@@ -65,6 +67,12 @@
6567

6668
FormatId = LegacyFormatId
6769

70+
_SELLER_AUTHORITATIVE_PARAM_KEYS = {
71+
"pixel_ratios",
72+
"required_pixel_ratios",
73+
"slots",
74+
}
75+
6876

6977
@dataclass
7078
class V1ToV2Projection:
@@ -267,10 +275,10 @@ def project_v1_format_to_declaration(
267275
# seller annotations can still pick up registry-published default
268276
# ``parameters`` without forcing the seller to restate them on the v1
269277
# file. Step 1 (seller annotation) overrides ``kind`` /
270-
# ``slots_override`` / ``asset_source`` but does NOT clobber the
271-
# registry's parametric defaults — that would lose every parameter
272-
# a registry glob carries (e.g., ``vast_version``, dimensions) when
273-
# a seller annotates only ``{kind: video_vast}``.
278+
# ``slots_override`` / ``asset_source``. Neutral registry parameters
279+
# such as dimensions still fill omitted values, but seller-authored
280+
# declarations must not inherit registry slot or pixel-density
281+
# contracts.
274282
registry_params: dict[str, Any] = {}
275283
registry_kind: CanonicalFormatKind | None = None
276284
for mapping in registry.mappings:
@@ -283,15 +291,21 @@ def project_v1_format_to_declaration(
283291

284292
# --- Step 1 (registry resolution-order step 2): seller-asserted
285293
# ``canonical`` annotation on the v1 file. Annotation wins on
286-
# ``kind`` + ``asset_source`` + ``slots_override``; registry
287-
# parameters fill in anything the seller didn't restate.
294+
# ``kind`` + ``asset_source`` + ``slots_override``; neutral registry
295+
# parameters fill omitted values without importing registry slot or
296+
# pixel-density contracts.
288297
annotation = _v1_canonical_annotation(v1_format)
289298
if annotation is not None:
299+
seller_params = {
300+
key: value
301+
for key, value in registry_params.items()
302+
if key not in _SELLER_AUTHORITATIVE_PARAM_KEYS
303+
}
290304
return V1ToV2Projection(
291305
declaration=_build_declaration(
292306
kind=annotation.kind,
293307
v1_format_id=fid,
294-
params=registry_params,
308+
params=seller_params,
295309
canonical_ref=annotation,
296310
)
297311
)

tests/test_canonical_formats_v1_to_v2.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,59 @@ def test_slots_override_threads_into_params() -> None:
8080
assert slots[0]["asset_group_id"] == "image_main"
8181

8282

83+
def test_seller_slots_override_registry_retina_slot_contract() -> None:
84+
v1 = {
85+
"format_id": {
86+
"agent_url": "https://creative.adcontextprotocol.org",
87+
"id": "display_300x250_image_1x_2x",
88+
},
89+
"canonical": {
90+
"kind": "image",
91+
"slots_override": [
92+
{
93+
"asset_group_id": "seller_image",
94+
"asset_type": "image",
95+
"required": True,
96+
}
97+
],
98+
},
99+
}
100+
101+
result = project_v1_format_to_declaration(v1)
102+
103+
assert result.declaration is not None
104+
assert result.declaration.params == {
105+
"width": 300,
106+
"height": 250,
107+
"slots": [
108+
{
109+
"asset_group_id": "seller_image",
110+
"asset_type": "image",
111+
"required": True,
112+
"consumed_for_production": True,
113+
}
114+
],
115+
}
116+
assert result.advisories == []
117+
118+
119+
def test_seller_kind_does_not_inherit_registry_retina_contract() -> None:
120+
v1 = {
121+
"format_id": {
122+
"agent_url": "https://creative.adcontextprotocol.org",
123+
"id": "display_300x250_image_1x_2x",
124+
},
125+
"canonical": {"kind": "html5"},
126+
}
127+
128+
result = project_v1_format_to_declaration(v1)
129+
130+
assert result.declaration is not None
131+
assert result.declaration.format_kind is CanonicalFormatKind.html5
132+
assert result.declaration.params == {"width": 300, "height": 250}
133+
assert result.advisories == []
134+
135+
83136
# ---------------------------------------------------------------------------
84137
# Step 2 — registry glob match (no explicit canonical)
85138
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)