Module adcp.canonical_formats.v2_to_v1
v2 → v1 canonical-format projection.
Projects Product.format_options[] (v2) into Product.format_ids[]
(v1) so v1-only buyers see the product without losing visibility, while
emitting non-fatal advisories on errors[] for the seller to act on.
Resolution order per registries/v1-canonical-mapping.json (the
"direction of truth" section is normative):
canonical_formats_only=True— no v1 emit and no advisory. The seller has explicitly opted out of v1 projection. Note thatProductFormatDeclarationenforces this is mutually exclusive withv1_format_ref[]at construction.v1_format_ref[]set — emit those refs intoformat_ids[]. Applies to everyformat_kindincludingcustom(a custom format MAY carry seller-asserted v1 refs). Ifparams.sizes[]count >v1_format_ref[]count, emitFORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE(advisory only; the partial coverage still ships).v1_format_ref[]absent AND the canonical'sv1_translatabledefault isFalse— no v1 emit, no advisory. Canonicalsagent_placement,sponsored_placement,responsive_creative,image_carousel, andcustom(without seller-asserted refs) are v1-unreachable by design; warning here would spam the wire.v1_format_ref[]absent AND the canonical is normallyv1_translatable=True— emitFORMAT_DECLARATION_V1_AMBIGUOUS. The SDK explicitly does NOT synthesize a v1format_idfrom a structural registry match; that would produce inter-SDK divergence on structurally-equal v2 declarations.
The registry is intentionally NOT consulted on the v2 → v1 path
(see "Direction of truth (normative)" in
registries/v1-canonical-mapping.json). The v1 → v2 reverse path
will consume it in a follow-up PR.
Functions
def project_declaration_to_v1(declaration: ProductFormatDeclaration,
*,
field_path: str = 'format_options[]',
product_id: str | None = None) ‑> V2ToV1Projection-
Expand source code
def project_declaration_to_v1( declaration: ProductFormatDeclaration, *, field_path: str = "format_options[]", product_id: str | None = None, ) -> V2ToV1Projection: """Project a single declaration to v1, emitting advisories per the resolution order documented at module level. Args: declaration: The v2 ``ProductFormatDeclaration`` to project. field_path: JSONPath-lite pointer surfaced on emitted advisories (e.g., ``products[0].format_options[2]``). The default points at the seller-published declaration without product context; callers wrapping a ``Product`` should pass the indexed form. product_id: Optional product identifier — surfaced in advisory ``details.product_id`` for buyer-side correlation. Returns: :class:`V2ToV1Projection` with the projected refs and any advisories the resolution order emitted. """ kind = declaration.format_kind refs = list(declaration.v1_format_ref or []) # Step 1: seller has explicitly opted out of v1 projection. # ``ProductFormatDeclaration`` enforces this is mutually exclusive # with ``v1_format_ref[]``, so we can't reach step 2 from here. if declaration.canonical_formats_only: return V2ToV1Projection() # Step 2: seller-asserted v1 link — emit refs, check multi-size fan-out. if refs: advisories: list[Error] = [] sizes_n = _params_sizes_count(declaration) if sizes_n > len(refs): details: dict[str, Any] = { "format_kind": kind.value, "v1_format_ref_count": len(refs), "sizes_count": sizes_n, } if product_id is not None: details["product_id"] = _echo_identifier(product_id) advisories.append( make_sdk_advisory( code="FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE", message=( f"v1_format_ref[] has {len(refs)} entries but params.sizes[] " f"declares {sizes_n} sizes — the partial v1 emission covers " f"only the referenced sizes. Seller SHOULD author one " f"v1_format_ref entry per size." ), field=field_path, details=details, suggestion=( "Add per-size v1_format_ref[] entries (one per params.sizes " "entry) to give v1-only buyers full size coverage." ), ) ) return V2ToV1Projection(format_ids=refs, advisories=advisories) # Step 3: canonical is not v1-translatable — silent. if not V1_TRANSLATABLE.get(kind, True): return V2ToV1Projection() # Step 4: canonical IS v1-translatable but seller didn't author refs. details = { "format_kind": kind.value, "reason": "no_v1_format_ref", } if product_id is not None: details["product_id"] = _echo_identifier(product_id) return V2ToV1Projection( advisories=[ make_sdk_advisory( code="FORMAT_DECLARATION_V1_AMBIGUOUS", message=( f"Canonical '{kind.value}' is normally v1-translatable but the " f"declaration carries no v1_format_ref[] — SDK cannot synthesize " f"a v1 format_id without seller assertion." ), field=field_path, details=details, suggestion=( "Add v1_format_ref[] pointing at the v1 named format(s) this " "declaration projects to (e.g., AAO-hosted formats at " "https://creative.adcontextprotocol.org or a platform-published " "adagents.json formats[] entry)." ), ) ] )Project a single declaration to v1, emitting advisories per the resolution order documented at module level.
Args
declaration- The v2
ProductFormatDeclarationto project. field_path- JSONPath-lite pointer surfaced on emitted advisories
(e.g.,
products[0].format_options[2]). The default points at the seller-published declaration without product context; callers wrapping aProductshould pass the indexed form. product_id- Optional product identifier — surfaced in advisory
details.product_idfor buyer-side correlation.
Returns
:class:
V2ToV1Projectionwith the projected refs and any advisories the resolution order emitted. def project_product_to_v1(product: Any, *, product_index: int | None = None) ‑> V2ToV1Projection-
Expand source code
def project_product_to_v1( product: Any, *, product_index: int | None = None, ) -> V2ToV1Projection: """Project every ``format_options[]`` entry on a ``Product`` to v1. Walks the product's declarations, applies :func:`project_declaration_to_v1` to each, and accumulates the aggregated refs + advisories. The product's existing v1 ``format_ids`` field is preserved by the caller — this helper produces the *additive* set that the seller publishes alongside seller-declared v1 ids. Args: product: A ``Product`` instance carrying ``format_options[]``. Duck-typed so the helper works against the wire response, adopter-typed wrappers, or in-progress builders. product_index: Optional zero-based index of the product within the enclosing ``Products[]`` array. When provided, advisories carry the indexed field path (``products[N].format_options[K]``) so multi-product responses don't collapse to ambiguous pointers. Returns: :class:`V2ToV1Projection` with the union of per-declaration results. """ declarations = getattr(product, "format_options", None) or [] product_id = getattr(product, "product_id", None) or getattr(product, "id", None) out = V2ToV1Projection() for i, decl in enumerate(declarations): prefix = ( f"products[{product_index}].format_options[{i}]" if product_index is not None else f"format_options[{i}]" ) result = project_declaration_to_v1( decl, field_path=prefix, product_id=product_id, ) out.format_ids.extend(result.format_ids) out.advisories.extend(result.advisories) return outProject every
format_options[]entry on aProductto v1.Walks the product's declarations, applies :func:
project_declaration_to_v1()to each, and accumulates the aggregated refs + advisories. The product's existing v1format_idsfield is preserved by the caller — this helper produces the additive set that the seller publishes alongside seller-declared v1 ids.Args
product- A
Productinstance carryingformat_options[]. Duck-typed so the helper works against the wire response, adopter-typed wrappers, or in-progress builders. product_index- Optional zero-based index of the product within the
enclosing
Products[]array. When provided, advisories carry the indexed field path (products[N].format_options[K]) so multi-product responses don't collapse to ambiguous pointers.
Returns
:class:
V2ToV1Projectionwith the union of per-declaration results.
Classes
class V2ToV1Projection (format_ids: list[FormatId] = <factory>,
advisories: list[Error] = <factory>)-
Expand source code
@dataclass class V2ToV1Projection: """Result of projecting one or more ``ProductFormatDeclaration``s to v1. Attributes: format_ids: v1 ``format_ids[]`` entries to dual-emit alongside the v2 ``format_options[]``. Empty when the declarations are all v1-unreachable (custom / canonical_formats_only / non-translatable canonicals). advisories: SDK-source ``errors[]`` entries to augment the response with. Each carries ``source="sdk"`` and ``sdk_id=<this SDK>``. """ format_ids: list[FormatId] = field(default_factory=list) advisories: list[Error] = field(default_factory=list)Result of projecting one or more
ProductFormatDeclarations to v1.Attributes
format_ids- v1
format_ids[]entries to dual-emit alongside the v2format_options[]. Empty when the declarations are all v1-unreachable (custom / canonical_formats_only / non-translatable canonicals). advisories- SDK-source
errors[]entries to augment the response with. Each carriessource="sdk"andsdk_id=<this SDK>.
Instance variables
var advisories : list[adcp.types.generated_poc.core.error.Error]var format_ids : list[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject]