Module adcp.types.canonical_decl
Wire-faithful ProductFormatDeclaration for the v2 catalog surface.
The upstream schema core/product-format-declaration.json is a
discriminated oneOf over 13 format_kind values, each binding
params to a canonical-specific schema. datamodel-code-generator
collapses this shape to a single class carrying only the shared
properties — format_kind and params disappear entirely because
they live on the per-variant branches.
That generated stub is unusable for canonical-formats: it can't carry
the discriminator the projection layer routes on, and it silently drops
params (extra='ignore') so adopters who construct a declaration
with a typed canonical body lose it on serialization.
This module replaces the public ProductFormatDeclaration symbol
with a hand-rolled class that:
- Carries all 9 shared properties the generator emits.
- Adds
format_kind: CanonicalFormatKind(the discriminator). - Adds
params: dict[str, Any]— the per-canonical body. Required per the upstream schema (required: ["format_kind", "params"]). Kept as an open dict at this level so the same class works across all 13 canonical kinds; callers needing typed access SHOULD use :meth:ProductFormatDeclaration.params_as()to validate against the typed canonical format class. - Sets
extra='allow'so futureProductFormatDeclarationfield additions in 3.1.x don't break round-trip through this model. Extra fields are scanned for credential-shaped key suffixes at construction time; presence of one raises (see_CREDENTIAL_SHAPED_KEY_SUFFIXES). - Enforces the schema's normative cross-field constraint that
canonical_formats_only=Trueandv1_format_ref[]are mutually exclusive (product-format-declaration.jsonallOf.notclause).
The generated class is preserved as ProductFormatDeclaration
for callers that need the original codegen output (validation hooks,
schema-loader cross-references). New code SHOULD import the
hand-rolled class via :mod:adcp.types.
Classes
class ProductFormatDeclaration (**data: Any)-
Expand source code
class ProductFormatDeclaration(AdCPBaseModel): """v2 catalog-side format declaration carrying the canonical discriminator. Wire-faithful Python representation of ``core/product-format-declaration.json``. See the module docstring for why this class replaces the codegen output. """ model_config = ConfigDict(extra="allow") format_kind: Annotated[ CanonicalFormatKind, Field(description="The canonical format kind this declaration declares."), ] params: Annotated[ dict[str, Any], Field( description=( "Per-canonical body. Shape varies by format_kind — see the " "canonical's own schema (``formats/canonical/<kind>.json``). " "Use :meth:`params_as` for typed access." ), ), ] capability_id: Annotated[ str | None, Field( description=( "Stable identifier for this declaration. REQUIRED when the " "parent product's format_options[] contains multiple " "declarations sharing the same format_kind." ), ), ] = None display_name: Annotated[ str | None, Field(description="Optional seller-controlled human-readable label."), ] = None applies_to_channels: Annotated[ list[MediaChannel] | None, Field( description=( "Optional subset of the parent product's channels to which " "this declaration applies." ), ), ] = None seller_preference: Annotated[ SellerPreference | None, Field(description="Soft routing hint within the accepted set."), ] = None canonical_formats_only: Annotated[ bool, Field( description=( "When true, this declaration has no clean v1 projection — " "SDKs MUST NOT synthesize a v1 format_id. Mutually exclusive " "with ``v1_format_ref``." ), ), ] = False experimental: Annotated[ bool, Field( description=("When true, THIS seller's specific declaration may not work as declared."), ), ] = False format_shape: Annotated[ str | None, Field( description=( "REQUIRED when format_kind='custom'; otherwise MUST be absent. " "Recognized format-shape-vocabulary entry." ), ), ] = None v1_format_ref: Annotated[ list[FormatReferenceStructuredObject] | None, Field( description=( "Authoritative v2 → v1 link as one or more v1 format_id " "({agent_url, id}) values. Mutually exclusive with " "``canonical_formats_only=True``." ), min_length=1, ), ] = None format_schema: Annotated[ PlatformExtensionReference | None, Field( description=( "REQUIRED when format_kind='custom'; otherwise MUST be absent. " "URI+digest reference to the custom shape's schema." ), ), ] = None @model_validator(mode="after") def _check_mutual_exclusion(self) -> Self: """Enforce the schema's ``allOf.not`` clause. ``product-format-declaration.json`` declares ``canonical_formats_only=True`` and ``v1_format_ref[]`` mutually exclusive. The Pydantic model rejects the combination at construction so the SDK never launders a wire-invalid declaration into a wire-valid one. """ if self.canonical_formats_only and self.v1_format_ref: raise ValueError( "ProductFormatDeclaration: canonical_formats_only=True is " "mutually exclusive with v1_format_ref[] — a declaration can " "EITHER assert no v1 projection OR link to v1 named formats, " "never both. See product-format-declaration.json#allOf.not." ) return self @model_validator(mode="after") def _reject_credential_shaped_extras(self) -> Self: """Fail-closed scan for credential-shaped keys in ``params`` + extras. ``params`` is an open dict and ``model_config['extra']='allow'`` means unknown top-level fields are stored on the instance. Both are adopter-controlled bags that round-trip through ``format_options[]`` responses and the idempotency replay cache. Mirrors the dispatcher's ``ctx_metadata`` credential gate. """ for bag_name, bag_value in ( ("params", self.params), ("extras", self.__pydantic_extra__), ): if bag_value is None: continue found = _walk_for_credential_keys(bag_value, path=bag_name) if found is not None: raise ValueError( f"ProductFormatDeclaration: {found!r} matches a " f"credential-shaped key suffix and will round-trip to " f"buyers via format_options[]. Move the value to " f"AuthInfo.credential or a typed credential class. " f"See CLAUDE.md → 'ctx_metadata: write-only credentials " f"prohibited' for the equivalent dispatch-side rule." ) return self def params_as(self, canonical_type: type[_TypedParams]) -> _TypedParams: """Validate ``params`` against the typed canonical-format class. Lets buyers and seller-side validators recover full typing on the per-canonical body — e.g., ``decl.params_as(CanonicalFormatImage)`` returns a ``CanonicalFormatImage`` with ``.sizes`` / ``.format`` / etc. narrowed. Raises :class:`pydantic.ValidationError` when ``params`` doesn't match the canonical's schema. Args: canonical_type: A Pydantic model class from the canonical vocabulary (e.g., :class:`adcp.types.CanonicalFormatImage`). Returns: An instance of ``canonical_type`` validated against ``params``. """ return canonical_type.model_validate(self.params)v2 catalog-side format declaration carrying the canonical discriminator.
Wire-faithful Python representation of
core/product-format-declaration.json. See the module docstring for why this class replaces the codegen output.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var applies_to_channels : list[adcp.types.generated_poc.enums.channels.MediaChannel] | Nonevar canonical_formats_only : boolvar capability_id : str | Nonevar display_name : str | Nonevar experimental : boolvar format_kind : adcp.types.generated_poc.core.canonical_format_kind.CanonicalFormatKindvar format_schema : adcp.types.generated_poc.core.platform_extension_ref.PlatformExtensionReference | Nonevar format_shape : str | Nonevar model_configvar params : dict[str, typing.Any]var seller_preference : adcp.types.generated_poc.core.product_format_declaration.SellerPreference | Nonevar v1_format_ref : list[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject] | None
Methods
def params_as(self, canonical_type: type[_TypedParams]) ‑> ~_TypedParams-
Expand source code
def params_as(self, canonical_type: type[_TypedParams]) -> _TypedParams: """Validate ``params`` against the typed canonical-format class. Lets buyers and seller-side validators recover full typing on the per-canonical body — e.g., ``decl.params_as(CanonicalFormatImage)`` returns a ``CanonicalFormatImage`` with ``.sizes`` / ``.format`` / etc. narrowed. Raises :class:`pydantic.ValidationError` when ``params`` doesn't match the canonical's schema. Args: canonical_type: A Pydantic model class from the canonical vocabulary (e.g., :class:`adcp.types.CanonicalFormatImage`). Returns: An instance of ``canonical_type`` validated against ``params``. """ return canonical_type.model_validate(self.params)Validate
paramsagainst the typed canonical-format class.Lets buyers and seller-side validators recover full typing on the per-canonical body — e.g.,
decl.params_as(CanonicalFormatImage)returns aCanonicalFormatImagewith.sizes/.format/ etc. narrowed. Raises :class:pydantic.ValidationErrorwhenparamsdoesn't match the canonical's schema.Args
canonical_type- A Pydantic model class from the canonical
vocabulary (e.g., :class:
CanonicalFormatImage).
Returns
An instance of
canonical_typevalidated againstparams.
class _GeneratedProductFormatDeclaration (**data: Any)-
Expand source code
class ProductFormatDeclaration(AdCPBaseModel): format_option_id: Annotated[ str | None, Field( description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." ), ] = None publisher_domain: Annotated[ str | None, Field( description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', ), ] = None display_name: Annotated[ str | None, Field( description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." ), ] = None applies_to_channels: Annotated[ list[channels.MediaChannel] | None, Field( description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." ), ] = None seller_preference: Annotated[ SellerPreference | None, Field( description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." ), ] = None canonical_formats_only: Annotated[ bool | None, Field( description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' ), ] = False experimental: Annotated[ bool | None, Field( description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." ), ] = False format_shape: Annotated[ str | None, Field( description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' ), ] = None v1_format_ref: Annotated[ list[format_id.FormatReferenceStructuredObject] | None, Field( description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/<platform>` + `id: <platform-format-name>` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/<platform>`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/<platform>` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", min_length=1, ), ] = None format_schema: Annotated[ platform_extension_ref.PlatformExtensionReference | None, Field( description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.' ), ] = NoneBase model for AdCP types with spec-compliant serialization.
Defaults to
extra='ignore'so unknown fields from newer spec versions are silently dropped rather than causing validation errors. Generated types whose schemas setadditionalProperties: trueoverride this withextra='allow'in their ownmodel_config.Set
ADCP_STRICT_VALIDATION=1in the environment ("1","true","yes","on"are accepted) to flip the default toextra='forbid'. Use this during spec upgrades to catch silently-dropped renamed fields in tests. See :func:_resolve_extra_policy.Important
The env var is resolved once at module import time. Set it in your shell or CI environment before
import adcpruns — mutatingos.environ["ADCP_STRICT_VALIDATION"]after the firstadcpimport has no effect on already-imported model classes (they captured the policy at class-body evaluation).Consumers who want per-model strict validation can override
model_configon their subclass.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.Ancestors
- AdCPBaseModel
- pydantic.main.BaseModel
Class variables
var applies_to_channels : list[adcp.types.generated_poc.enums.channels.MediaChannel] | Nonevar canonical_formats_only : bool | Nonevar display_name : str | Nonevar experimental : bool | Nonevar format_option_id : str | Nonevar format_schema : adcp.types.generated_poc.core.platform_extension_ref.PlatformExtensionReference | Nonevar format_shape : str | Nonevar model_configvar publisher_domain : str | Nonevar seller_preference : adcp.types.generated_poc.core.product_format_declaration.SellerPreference | Nonevar v1_format_ref : list[adcp.types.generated_poc.core.format_id.FormatReferenceStructuredObject] | None
Inherited members